diff --git a/.env.example b/.env.example new file mode 100644 index 00000000..606b9714 --- /dev/null +++ b/.env.example @@ -0,0 +1,52 @@ +APP_ENV=local +APP_DEBUG=true +APP_KEY=SomeRandomString +APP_TIMEZONE=UTC +APP_LANG=en +APP_LOG=daily + +DB_CONNECTION=mysql +DB_HOST=127.0.0.1 +DB_PORT=3306 +DB_DATABASE=homestead +DB_USERNAME=homestead +DB_PASSWORD=secret +DB_CONNECTION=pgsql + +CACHE_DRIVER=file +SESSION_DRIVER=file +QUEUE_DRIVER=sync + +REDIS_HOST=127.0.0.1 +REDIS_PASSWORD=null +REDIS_PORT=6379 + +MAIL_DRIVER=smtp +MAIL_HOST=mailtrap.io +MAIL_PORT=2525 +MAIL_USERNAME=null +MAIL_PASSWORD=null +MAIL_ENCRYPTION=null +MAILGUN_DOMAIN=null +MAILGUN_SECRET=null + +AWS_S3_KEY=your-key +AWS_S3_SECRET=your-secret +AWS_S3_REGION=region +AWS_S3_BUCKET=your-bucket +AWS_S3_URL=https://xxxxxxx.s3-region.amazonaws.com + +APP_URL=https://example.com +APP_LONGURL=example.com +APP_SHORTURL=examp.le + +ADMIN_USER=admin +ADMIN_PASS=password + +PIWIK_URL= +PIWIK_SITE_ID= + +TWITTER_CONSUMER_KEY= +TWITTER_CONSUMER_SECRET= +TWITTER_ACCESS_TOKEN= +TWITTER_ACCESS_TOKEN_SECRET= diff --git a/.env.travis b/.env.travis new file mode 100644 index 00000000..b15c8b3a --- /dev/null +++ b/.env.travis @@ -0,0 +1,11 @@ +APP_ENV=testing +APP_KEY= +APP_URL=http://localhost:8000 +APP_LONGURL=localhost +APP_SHORTURL=local + +DB_CONNECTION=travis + +CACHE_DRIVER=array +SESSION_DRIVER=array +QUEUE_DRIVER=sync diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 00000000..a8763f8e --- /dev/null +++ b/.gitattributes @@ -0,0 +1,3 @@ +* text=auto +*.css linguist-vendored +*.scss linguist-vendored diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..f791bec4 --- /dev/null +++ b/.gitignore @@ -0,0 +1,11 @@ +/vendor +/node_modules +/bower_components +/public/storage +Homestead.yaml +Homestead.json +.env +/.sass-cache +/public/files +/public/keybase.txt +/coverage diff --git a/.styleci.yml b/.styleci.yml new file mode 100644 index 00000000..645435d1 --- /dev/null +++ b/.styleci.yml @@ -0,0 +1,7 @@ +preset: laravel + +disabled: + - concat_without_spaces + +finder: + path: app/ diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 00000000..cd687b56 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,42 @@ +language: php + +sudo: false + +addons: + postgresql: "9.4" + +services: + - postgresql + +env: + global: + - setup=basic + +php: + - 7.0 + - nightly +matrix: + allow_failures: + - php: nightly + +before_install: + - phpenv config-rm xdebug.ini + - travis_retry composer self-update --preview + +install: + - if [[ $setup = 'basic' ]]; then travis_retry composer install --no-interaction --prefer-dist; fi + - if [[ $setup = 'stable' ]]; then travis_retry composer update --no-interaction --prefer-dist --prefer-stable; fi + - if [[ $setup = 'lowest' ]]; then travis_retry composer update --no-interaction --prefer-dist --prefer-lowest --prefer-stable; fi + +before_script: + - psql -U travis -c 'create database travis_ci_test' + - psql -U travis -d travis_ci_test -c 'create extension postgis' + - cp .env.travis .env + - php artisan key:generate + - php artisan migrate + - php artisan db:seed + - php artisan serve & + - sleep 5 # Give artisan some time to start serving + +script: + - phpdbg -qrr vendor/bin/phpunit --coverage-text diff --git a/app/Article.php b/app/Article.php new file mode 100644 index 00000000..5ca3f582 --- /dev/null +++ b/app/Article.php @@ -0,0 +1,148 @@ +morphMany('App\WebMention', 'commentable'); + } + + /** + * We shall set a blacklist of non-modifiable model attributes. + * + * @var array + */ + protected $guarded = ['id']; + + /** + * Process the article for display. + * + * @return string + */ + public function getMainAttribute($value) + { + $unicode = new UnicodeTools(); + $markdown = new CommonMarkConverter(); + $html = $markdown->convertToHtml($unicode->convertUnicodeCodepoints($value)); + //change
[lang] ~> 

+        $match = '/
\[(.*)\]\n/';
+        $replace = '
';
+        $text = preg_replace($match, $replace, $html);
+        $default = preg_replace('/
/', '
', $text);
+
+        return $default;
+    }
+
+    /**
+     * Convert updated_at to W3C time format.
+     *
+     * @return string
+     */
+    public function getW3cTimeAttribute()
+    {
+        return $this->updated_at->toW3CString();
+    }
+
+    /**
+     * Convert updated_at to a tooltip appropriate format.
+     *
+     * @return string
+     */
+    public function getTooltipTimeAttribute()
+    {
+        return $this->updated_at->toRFC850String();
+    }
+
+    /**
+     * Convert updated_at to a human readable format.
+     *
+     * @return string
+     */
+    public function getHumanTimeAttribute()
+    {
+        return $this->updated_at->diffForHumans();
+    }
+
+    /**
+     * Get the pubdate value for RSS feeds.
+     *
+     * @return string
+     */
+    public function getPubdateAttribute()
+    {
+        return $this->updated_at->toRSSString();
+    }
+
+    /**
+     * A link to the article, i.e. `/blog/1999/12/25/merry-christmas`.
+     *
+     * @return string
+     */
+    public function getLinkAttribute()
+    {
+        return '/blog/' . $this->updated_at->year . '/' . $this->updated_at->format('m') . '/' . $this->titleurl;
+    }
+
+    /**
+     * Scope a query to only include articles from a particular year/month.
+     *
+     * @return \Illuminate\Database\Eloquent\Builder
+     */
+    public function scopeDate($query, $year = null, $month = null)
+    {
+        if ($year == null) {
+            return $query;
+        }
+        $start = $year . '-01-01 00:00:00';
+        $end = ($year + 1) . '-01-01 00:00:00';
+        if (($month !== null) && ($month !== '12')) {
+            $start = $year . '-' . $month . '-01 00:00:00';
+            $end = $year . '-' . ($month + 1) . '-01 00:00:00';
+        }
+        if ($month === '12') {
+            $start = $year . '-12-01 00:00:00';
+            //$end as above
+        }
+
+        return $query->where([
+            ['updated_at', '>=', $start],
+            ['updated_at', '<', $end],
+        ]);
+    }
+}
diff --git a/app/Client.php b/app/Client.php
new file mode 100644
index 00000000..6461399c
--- /dev/null
+++ b/app/Client.php
@@ -0,0 +1,22 @@
+comment(PHP_EOL.Inspiring::quote().PHP_EOL);
+    }
+}
diff --git a/app/Console/Kernel.php b/app/Console/Kernel.php
new file mode 100644
index 00000000..71c519d3
--- /dev/null
+++ b/app/Console/Kernel.php
@@ -0,0 +1,30 @@
+command('inspire')
+        //          ->hourly();
+    }
+}
diff --git a/app/Contact.php b/app/Contact.php
new file mode 100644
index 00000000..18cc6fa2
--- /dev/null
+++ b/app/Contact.php
@@ -0,0 +1,22 @@
+renderExceptionWithWhoops($exc);
+        }
+
+        if ($exc instanceof ModelNotFoundException) {
+            $exc = new NotFoundHttpException($exc->getMessage(), $exc);
+        }
+
+        if ($exc instanceof TokenMismatchException) {
+            return redirect()->back()
+                ->withInput($request->except('password', '_token'))
+                ->withErrors('Validation Token has expired. Please try again', 'csrf');
+        }
+
+        return parent::render($request, $exc);
+    }
+
+    /**
+     * Render an exception using Whoops.
+     *
+     * @param  \Exception $exc
+     * @return \Illuminate\Http\Response
+     */
+    protected function renderExceptionWithWhoops(Exception $exc)
+    {
+        $whoops = new \Whoops\Run;
+        $handler = new \Whoops\Handler\PrettyPageHandler();
+        $handler->setEditor(function ($file, $line) {
+            return "atom://open?file=$file&line=$line";
+        });
+        $whoops->pushHandler($handler);
+
+        return new \Illuminate\Http\Response(
+            $whoops->handleException($exc),
+            $exc->getStatusCode(),
+            $exc->getHeaders()
+        );
+    }
+}
diff --git a/app/Exceptions/RemoteContentNotFound.php b/app/Exceptions/RemoteContentNotFound.php
new file mode 100644
index 00000000..04049903
--- /dev/null
+++ b/app/Exceptions/RemoteContentNotFound.php
@@ -0,0 +1,10 @@
+username = env('ADMIN_USER');
+    }
+
+    /**
+     * Show the main admin CP page.
+     *
+     * @return \Illuminate\View\Factory view
+     */
+    public function showWelcome()
+    {
+        return view('admin.welcome', ['name' => $this->username]);
+    }
+}
diff --git a/app/Http/Controllers/ArticlesAdminController.php b/app/Http/Controllers/ArticlesAdminController.php
new file mode 100644
index 00000000..21f4124d
--- /dev/null
+++ b/app/Http/Controllers/ArticlesAdminController.php
@@ -0,0 +1,140 @@
+ $message]);
+    }
+
+    /**
+     * List the articles that can be edited.
+     *
+     * @return \Illuminate\View\Factory view
+     */
+    public function listArticles()
+    {
+        $posts = Article::select('id', 'title', 'published')->orderBy('id', 'desc')->get();
+
+        return view('admin.listarticles', ['posts' => $posts]);
+    }
+
+    /**
+     * Show the edit form for an existing article.
+     *
+     * @param  string  The article id
+     * @return \Illuminate\View\Factory view
+     */
+    public function editArticle($articleId)
+    {
+        $post = Article::select(
+            'title',
+            'main',
+            'url',
+            'published'
+        )->where('id', $articleId)->get();
+
+        return view('admin.editarticle', ['id' => $articleId, 'post' => $post]);
+    }
+
+    /**
+     * Show the delete confirmation form for an article.
+     *
+     * @param  string  The article id
+     * @return \Illuminate\View\Factory view
+     */
+    public function deleteArticle($articleId)
+    {
+        return view('admin.deletearticle', ['id' => $articleId]);
+    }
+
+    /**
+     * Process an incoming request for a new article and save it.
+     *
+     * @param  \Illuminate\Http\Request $request
+     * @return \Illuminate\View\Factory view
+     */
+    public function postNewArticle(Request $request)
+    {
+        $published = $request->input('published');
+        if ($published == null) {
+            $published = '0';
+        }
+        //if a `.md` is attached use that for the main content.
+        $content = null; //set default value
+        if ($request->hasFile('article')) {
+            $file = $request->file('article')->openFile();
+            $content = $file->fread($file->getSize());
+        }
+        $main = $content ?? $request->input('main');
+        try {
+            $article = Article::create(
+                [
+                    'url' => $request->input('url'),
+                    'title' => $request->input('title'),
+                    'main' => $main,
+                    'published' => $published,
+                ]
+            );
+        } catch (Exception $e) {
+            $msg = $e->getMessage();
+            $unique = strpos($msg, '1062');
+            if ($unique !== false) {
+                //We've checked for error 1062, i.e. duplicate titleurl
+                return redirect('admin/blog/new')->withInput()->with('message', 'Duplicate title, please change');
+            }
+            //this isn't the error you're looking for
+            throw $e;
+        }
+
+        return view('admin.newarticlesuccess', ['id' => $article->id, 'title' => $article->title]);
+    }
+
+    /**
+     * Process an incoming request to edit an article.
+     *
+     * @param  string
+     * @param  \Illuminate\Http\Request $request
+     * @return \Illuminate|View\Factory view
+     */
+    public function postEditArticle($articleId, Request $request)
+    {
+        $published = $request->input('published');
+        if ($published == null) {
+            $published = '0';
+        }
+        $article = Article::find($articleId);
+        $article->title = $request->input('title');
+        $article->url = $request->input('url');
+        $article->main = $request->input('main');
+        $article->published = $published;
+        $article->save();
+
+        return view('admin.editarticlesuccess', ['id' => $articleId]);
+    }
+
+    /**
+     * Process a request to delete an aricle.
+     *
+     * @param  string The article id
+     * @return \Illuminate\View\Factory view
+     */
+    public function postDeleteArticle($articleId)
+    {
+        Article::where('id', $articleId)->delete();
+
+        return view('admin.deletearticlesuccess', ['id' => $articleId]);
+    }
+}
diff --git a/app/Http/Controllers/ArticlesController.php b/app/Http/Controllers/ArticlesController.php
new file mode 100644
index 00000000..95ee9453
--- /dev/null
+++ b/app/Http/Controllers/ArticlesController.php
@@ -0,0 +1,69 @@
+date($year, $month)
+                    ->orderBy('updated_at', 'desc')
+                    ->simplePaginate(5);
+
+        return view('multipost', ['data' => $articles]);
+    }
+
+    /**
+     * Show a single article.
+     *
+     * @return \Illuminate\View\Factory view
+     */
+    public function singleArticle($year, $month, $slug)
+    {
+        $article = Article::where('titleurl', $slug)->first();
+        if ($article->updated_at->year != $year || $article->updated_at->month != $month) {
+            throw new \Exception;
+        }
+
+        return view('singlepost', ['article' => $article]);
+    }
+
+    /**
+     * We only have the ID, work out post title, year and month
+     * and redirect to it.
+     *
+     * @return \Illuminte\Routing\RedirectResponse redirect
+     */
+    public function onlyIdInUrl($inURLId)
+    {
+        $numbers = new Numbers();
+        $realId = $numbers->b60tonum($inURLId);
+        $article = Article::findOrFail($realId);
+
+        return redirect($article->link);
+    }
+
+    /**
+     * Returns the RSS feed.
+     *
+     * @return \Illuminate\Http\Response
+     */
+    public function makeRSS()
+    {
+        $articles = Article::where('published', '1')->orderBy('updated_at', 'desc')->get();
+        $buildDate = $articles->first()->updated_at->toRssString();
+        $contents = (string) view('rss', ['articles' => $articles, 'buildDate' => $buildDate]);
+
+        return (new Response($contents, '200'))->header('Content-Type', 'application/rss+xml');
+    }
+}
diff --git a/app/Http/Controllers/Auth/AuthController.php b/app/Http/Controllers/Auth/AuthController.php
new file mode 100644
index 00000000..a100dd6e
--- /dev/null
+++ b/app/Http/Controllers/Auth/AuthController.php
@@ -0,0 +1,72 @@
+middleware($this->guestMiddleware(), ['except' => 'logout']);
+    }
+
+    /**
+     * Get a validator for an incoming registration request.
+     *
+     * @param  array  $data
+     * @return \Illuminate\Contracts\Validation\Validator
+     */
+    protected function validator(array $data)
+    {
+        return Validator::make($data, [
+            'name' => 'required|max:255',
+            'email' => 'required|email|max:255|unique:users',
+            'password' => 'required|min:6|confirmed',
+        ]);
+    }
+
+    /**
+     * Create a new user instance after a valid registration.
+     *
+     * @param  array  $data
+     * @return User
+     */
+    protected function create(array $data)
+    {
+        return User::create([
+            'name' => $data['name'],
+            'email' => $data['email'],
+            'password' => bcrypt($data['password']),
+        ]);
+    }
+}
diff --git a/app/Http/Controllers/Auth/PasswordController.php b/app/Http/Controllers/Auth/PasswordController.php
new file mode 100644
index 00000000..1ceed97b
--- /dev/null
+++ b/app/Http/Controllers/Auth/PasswordController.php
@@ -0,0 +1,32 @@
+middleware('guest');
+    }
+}
diff --git a/app/Http/Controllers/AuthController.php b/app/Http/Controllers/AuthController.php
new file mode 100644
index 00000000..f752fa21
--- /dev/null
+++ b/app/Http/Controllers/AuthController.php
@@ -0,0 +1,29 @@
+input('username') === env('ADMIN_USER')
+            &&
+            $request->input('password') === env('ADMIN_PASS')
+        ) {
+            session(['loggedin' => true]);
+
+            return redirect()->intended('admin');
+        }
+
+        return redirect()->route('login');
+    }
+}
diff --git a/app/Http/Controllers/ClientsAdminController.php b/app/Http/Controllers/ClientsAdminController.php
new file mode 100644
index 00000000..c374aa24
--- /dev/null
+++ b/app/Http/Controllers/ClientsAdminController.php
@@ -0,0 +1,87 @@
+ $clients]);
+    }
+
+    /**
+     * Show form to add a client name.
+     *
+     * @return \Illuminate\View\Factory view
+     */
+    public function newClient()
+    {
+        return view('admin.newclient');
+    }
+
+    /**
+     * Process the request to adda new client name.
+     *
+     * @param  \Illuminate\Http\Request $request
+     * @return \Illuminate\View\Factory view
+     */
+    public function postNewClient(Request $request)
+    {
+        Client::create([
+            'client_url' => $request->input('client_url'),
+            'client_name' => $request->input('client_name'),
+        ]);
+
+        return view('admin.newclientsuccess');
+    }
+
+    /**
+     * Show a form to edit a client name.
+     *
+     * @param  string The client id
+     * @return \Illuminate\View\Factory view
+     */
+    public function editClient($clientId)
+    {
+        $client = Client::findOrFail($clientId);
+
+        return view('admin.editclient', [
+            'id' => $clientId,
+            'client_url' => $client->client_url,
+            'client_name' => $client->client_name,
+        ]);
+    }
+
+    /**
+     * Process the request to edit a client name.
+     *
+     * @param  string  The client id
+     * @param  \Illuminate\Http\Request $request
+     * @return \Illuminate\View\Factory view
+     */
+    public function postEditClient($clientId, Request $request)
+    {
+        $client = Client::findOrFail($clientId);
+        if ($request->input('edit')) {
+            $client->client_url = $request->input('client_url');
+            $client->client_name = $request->input('client_name');
+            $client->save();
+
+            return view('admin.editclientsuccess');
+        }
+        if ($request->input('delete')) {
+            $client->delete();
+
+            return view('admin.deleteclientsuccess');
+        }
+    }
+}
diff --git a/app/Http/Controllers/ContactsAdminController.php b/app/Http/Controllers/ContactsAdminController.php
new file mode 100644
index 00000000..890f1241
--- /dev/null
+++ b/app/Http/Controllers/ContactsAdminController.php
@@ -0,0 +1,166 @@
+ $contacts]);
+    }
+
+    /**
+     * Show the form to edit an existing contact.
+     *
+     * @param  string  The contact id
+     * @return \Illuminate\View\Factory view
+     */
+    public function editContact($contactId)
+    {
+        $contact = Contact::findOrFail($contactId);
+
+        return view('admin.editcontact', ['contact' => $contact]);
+    }
+
+    /**
+     * Show the form to confirm deleting a contact.
+     *
+     * @return \Illuminate\View\Factory view
+     */
+    public function deleteContact($contactId)
+    {
+        return view('admin.deletecontact', ['id' => $contactId]);
+    }
+
+    /**
+     * Process the request to add a new contact.
+     *
+     * @param  \Illuminate\Http|request $request
+     * @return \Illuminate\View\Factory view
+     */
+    public function postNewContact(Request $request)
+    {
+        $contact = new Contact();
+        $contact->name = $request->input('name');
+        $contact->nick = $request->input('nick');
+        $contact->homepage = $request->input('homepage');
+        $contact->twitter = $request->input('twitter');
+        $contact->save();
+        $contactId = $contact->id;
+
+        return view('admin.newcontactsuccess', ['id' => $contactId]);
+    }
+
+    /**
+     * Process the request to edit a contact.
+     *
+     * @todo   Allow saving profile pictures for people without homepages
+     *
+     * @param  string  The contact id
+     * @param  \Illuminate\Http\Request $request
+     * @return \Illuminate\View\Factory view
+     */
+    public function postEditContact($contactId, Request $request)
+    {
+        $contact = Contact::findOrFail($contactId);
+        $contact->name = $request->input('name');
+        $contact->nick = $request->input('nick');
+        $contact->homepage = $request->input('homepage');
+        $contact->twitter = $request->input('twitter');
+        $contact->save();
+
+        if ($request->hasFile('avatar')) {
+            if ($request->input('homepage') != '') {
+                $dir = parse_url($request->input('homepage'))['host'];
+                $destination = public_path() . '/assets/profile-images/' . $dir;
+                $filesystem = new Filesystem();
+                if ($filesystem->isDirectory($destination) === false) {
+                    $filesystem->makeDirectory($destination);
+                }
+                $request->file('avatar')->move($destination, 'image');
+            }
+        }
+
+        return view('admin.editcontactsuccess');
+    }
+
+    /**
+     * Process the request to delete a contact.
+     *
+     * @param  string  The contact id
+     * @return \Illuminate\View\Factory view
+     */
+    public function postDeleteContact($contactId)
+    {
+        $contact = Contact::findOrFail($contactId);
+        $contact->delete();
+
+        return view('admin.deletecontactsuccess');
+    }
+
+    /**
+     * Download the avatar for a contact.
+     *
+     * This method attempts to find the microformat marked-up profile image
+     * from a given homepage and save it accordingly
+     *
+     * @param  string  The contact id
+     * @return \Illuminate\View\Factory view
+     */
+    public function getAvatar($contactId)
+    {
+        $contact = Contact::findOrFail($contactId);
+        $homepage = $contact->homepage;
+        if (($homepage !== null) && ($homepage !== '')) {
+            $client = new Client();
+            try {
+                $response = $client->get($homepage);
+                $html = (string) $response->getBody();
+                $mf2 = \Mf2\parse($html, $homepage);
+            } catch (\GuzzleHttp\Exception\BadResponseException $e) {
+                return "Bad Response from $homepage";
+            }
+            $avatarURL = null; // Initialising
+            foreach ($mf2['items'] as $microformat) {
+                if ($microformat['type'][0] == 'h-card') {
+                    $avatarURL = $microformat['properties']['photo'][0];
+                    break;
+                }
+            }
+            try {
+                $avatar = $client->get($avatarURL);
+            } catch (\GuzzleHttp\Exception\BadResponseException $e) {
+                return "Unable to get $avatarURL";
+            }
+            $directory = public_path() . '/assets/profile-images/' . parse_url($homepage)['host'];
+            $filesystem = new Filesystem();
+            if ($filesystem->isDirectory($directory) === false) {
+                $filesystem->makeDirectory($directory);
+            }
+            $filesystem->put($directory . '/image', $avatar->getBody());
+
+            return view('admin.getavatarsuccess', ['homepage' => parse_url($homepage)['host']]);
+        }
+    }
+}
diff --git a/app/Http/Controllers/ContactsController.php b/app/Http/Controllers/ContactsController.php
new file mode 100644
index 00000000..01528411
--- /dev/null
+++ b/app/Http/Controllers/ContactsController.php
@@ -0,0 +1,49 @@
+homepagePretty = parse_url($contact->homepage)['host'];
+            $file = public_path() . '/assets/profile-images/' . $contact->homepagePretty . '/image';
+            $contact->image = ($filesystem->exists($file)) ?
+                '/assets/profile-images/' . $contact->homepagePretty . '/image'
+            :
+                '/assets/profile-images/default-image';
+        }
+
+        return view('contacts', ['contacts' => $contacts]);
+    }
+
+    /**
+     * Show a single contact.
+     *
+     * @return \Illuminate\View\Factory view
+     */
+    public function showSingle($nick)
+    {
+        $filesystem = new Filesystem();
+        $contact = Contact::where('nick', '=', $nick)->firstOrFail();
+        $contact->homepagePretty = parse_url($contact->homepage)['host'];
+        $file = public_path() . '/assets/profile-images/' . $contact->homepagePretty . '/image';
+        $contact->image = ($filesystem->exists($file)) ?
+            '/assets/profile-images/' . $contact->homepagePretty . '/image'
+        :
+            '/assets/profile-images/default-image';
+
+        return view('contact', ['contact' => $contact]);
+    }
+}
diff --git a/app/Http/Controllers/Controller.php b/app/Http/Controllers/Controller.php
new file mode 100644
index 00000000..d492e0b3
--- /dev/null
+++ b/app/Http/Controllers/Controller.php
@@ -0,0 +1,14 @@
+indieAuthService = $indieAuthService ?? new IndieAuthService();
+        $this->client = $client ?? new Client();
+        $this->tokenService = $tokenService ?? new TokenService();
+    }
+
+    /**
+     * Begin the indie auth process. This method ties in to the login page
+     * from our micropub client. Here we then query the user’s homepage
+     * for their authorisation endpoint, and redirect them there with a
+     * unique secure state value.
+     *
+     * @param  \Illuminate\Http\Request $request
+     * @return \Illuminate\Routing\RedirectResponse redirect
+     */
+    public function beginauth(Request $request)
+    {
+        $authorizationEndpoint = $this->indieAuthService->getAuthorizationEndpoint(
+            $request->input('me'),
+            $this->client
+        );
+        if ($authorizationEndpoint) {
+            $authorizationURL = $this->indieAuthService->buildAuthorizationURL(
+                $authorizationEndpoint,
+                $request->input('me'),
+                $this->client
+            );
+            if ($authorizationURL) {
+                return redirect($authorizationURL);
+            }
+        }
+
+        return redirect('/notes/new')->withErrors('Unable to determine authorisation endpoint', 'indieauth');
+    }
+
+    /**
+     * Once they have verified themselves through the authorisation endpint
+     * the next step is retreiveing a token from the token endpoint.
+     *
+     * @param  \Illuminate\Http\Rrequest $request
+     * @return \Illuminate\Routing\RedirectResponse redirect
+     */
+    public function indieauth(Request $request)
+    {
+        if ($request->session()->get('state') != $request->input('state')) {
+            return redirect('/notes/new')->withErrors(
+                'Invalid state value returned from indieauth server',
+                'indieauth'
+            );
+        }
+        $tokenEndpoint = $this->indieAuthService->getTokenEndpoint($request->input('me'), $this->client);
+        $redirectURL = config('app.url') . '/indieauth';
+        $clientId = config('app.url') . '/notes/new';
+        $data = [
+            'endpoint' => $tokenEndpoint,
+            'code' => $request->input('code'),
+            'me' => $request->input('me'),
+            'redirect_url' => $redirectURL,
+            'client_id' => $clientId,
+            'state' => $request->input('state'),
+        ];
+        $token = $this->indieAuthService->getAccessToken($data, $this->client);
+
+        if (array_key_exists('access_token', $token)) {
+            $request->session()->put('me', $token['me']);
+            $request->session()->put('token', $token['access_token']);
+
+            return redirect('/notes/new');
+        }
+
+        return redirect('/notes/new')->withErrors('Unable to get a token from the endpoint', 'indieauth');
+    }
+
+    /**
+     * If the user has auth’d via IndieAuth, issue a valid token.
+     *
+     * @param  \Illuminate\Http\Request $request
+     * @return \Illuminate\Http\Response
+     */
+    public function tokenEndpoint(Request $request)
+    {
+        $authData = [
+            'code' => $request->input('code'),
+            'me' => $request->input('me'),
+            'redirect_url' => $request->input('redirect_uri'),
+            'client_id' => $request->input('client_id'),
+            'state' => $request->input('state'),
+        ];
+        $auth = $this->indieAuthService->verifyIndieAuthCode($authData, $this->client);
+        if (array_key_exists('me', $auth)) {
+            $scope = $auth['scope'] ?? '';
+            $tokenData = [
+                'me' => $request->input('me'),
+                'client_id' => $request->input('client_id'),
+                'scope' => $auth['scope'],
+            ];
+            $token = $this->tokenService->getNewToken($tokenData);
+            $content = http_build_query([
+                'me' => $request->input('me'),
+                'scope' => $scope,
+                'access_token' => $token,
+            ]);
+
+            return (new Response($content, 200))
+                           ->header('Content-Type', 'application/x-www-form-urlencoded');
+        }
+        $content = 'There was an error verifying the authorisation code.';
+
+        return new Response($content, 400);
+    }
+
+    /**
+     * Log out the user, flush an session data, and overwrite any cookie data.
+     *
+     * @param  \Illuminate\Cookie\CookieJar $cookie
+     * @return \Illuminate\Routing\RedirectResponse redirect
+     */
+    public function indieauthLogout(Request $request, CookieJar $cookie)
+    {
+        $request->session()->flush();
+        $cookie->queue('me', 'loggedout', 5);
+
+        return redirect('/notes/new');
+    }
+}
diff --git a/app/Http/Controllers/MicropubClientController.php b/app/Http/Controllers/MicropubClientController.php
new file mode 100644
index 00000000..9978d985
--- /dev/null
+++ b/app/Http/Controllers/MicropubClientController.php
@@ -0,0 +1,333 @@
+indieAuthService = $indieAuthService ?? new IndieAuthService();
+        $this->guzzleClient = $guzzleClient ?? new GuzzleClient();
+        $this->indieClient = $indieClient ?? new IndieClient();
+    }
+
+    /**
+     * Display the new notes form.
+     *
+     * @param  \Illuminate\Http\Request $request
+     * @return \Illuminate\View\Factory view
+     */
+    public function newNotePage(Request $request)
+    {
+        $url = $request->session()->get('me');
+        $syndication = $this->parseSyndicationTargets(
+            $request->session()->get('syndication')
+        );
+
+        return view('micropubnewnotepage', [
+            'url' => $url,
+            'syndication' => $syndication,
+        ]);
+    }
+
+    /**
+     * Post the notes content to the relavent micropub API endpoint.
+     *
+     * @todo   make sure this works with multiple syndication targets
+     *
+     * @param  \Illuminate\Http\Request $request
+     * @return mixed
+     */
+    public function postNewNote(Request $request)
+    {
+        $domain = $request->session()->get('me');
+        $token = $request->session()->get('token');
+
+        $micropubEndpoint = $this->indieAuthService->discoverMicropubEndpoint(
+            $domain,
+            $this->indieClient
+        );
+        if (! $micropubEndpoint) {
+            return redirect('notes/new')->withErrors('Unable to determine micropub API endpoint', 'endpoint');
+        }
+
+        $response = $this->postNoteRequest($request, $micropubEndpoint, $token);
+
+        if ($response->getStatusCode() == 201) {
+            $location = $response->getHeader('Location');
+            if (is_array($location)) {
+                return redirect($location[0]);
+            }
+
+            return redirect($location);
+        }
+
+        return redirect('notes/new')->withErrors('Endpoint didn’t create the note.', 'endpoint');
+    }
+
+    /**
+     * We make a request to the micropub endpoint requesting syndication targets
+     * and store them in the session.
+     *
+     * @todo better handling of response regarding mp-syndicate-to
+     *       and syndicate-to
+     *
+     * @param  \Illuminate\Http\Request $request
+     * @param  \IndieAuth\Client $indieClient
+     * @param  \GuzzleHttp\Client $guzzleClient
+     * @return \Illuminate\Routing\Redirector redirect
+     */
+    public function refreshSyndicationTargets(Request $request)
+    {
+        $domain = $request->session()->get('me');
+        $token = $request->session()->get('token');
+        $micropubEndpoint = $this->indieAuthService->discoverMicropubEndpoint($domain, $this->indieClient);
+
+        if (! $micropubEndpoint) {
+            return redirect('notes/new')->withErrors('Unable to determine micropub API endpoint', 'endpoint');
+        }
+
+        try {
+            $response = $this->guzzleClient->get($micropubEndpoint, [
+                'headers' => ['Authorization' => 'Bearer ' . $token],
+                'query' => ['q' => 'syndicate-to'],
+            ]);
+        } catch (\GuzzleHttp\Exception\BadResponseException $e) {
+            return redirect('notes/new')->withErrors('Bad response when refreshing syndication targets', 'endpoint');
+        }
+        $body = (string) $response->getBody();
+        $syndication = str_replace(['&', '[]'], [';', ''], $body);
+
+        $request->session()->put('syndication', $syndication);
+
+        return redirect('notes/new');
+    }
+
+    /**
+     * This method performs the actual POST request.
+     *
+     * @param  \Illuminate\Http\Request $request
+     * @param  string The Micropub endpoint to post to
+     * @param  string The token to authenticate the request with
+     * @return \GuzzleHttp\Response $response | \Illuminate\RedirectFactory redirect
+     */
+    private function postNoteRequest(
+        Request $request,
+        $micropubEndpoint,
+        $token
+    ) {
+        $multipart = [
+            [
+                'name' => 'h',
+                'contents' => 'entry',
+            ],
+            [
+                'name' => 'content',
+                'contents' => $request->input('content'),
+            ],
+        ];
+        if ($request->hasFile('photo')) {
+            $photos = $request->file('photo');
+            foreach ($photos as $photo) {
+                $filename = $photo->getClientOriginalName();
+                $photo->move(storage_path() . '/media-tmp', $filename);
+                $multipart[] = [
+                    'name' => 'photo[]',
+                    'contents' => fopen(storage_path() . '/media-tmp/' . $filename, 'r'),
+                ];
+            }
+        }
+        if ($request->input('in-reply-to') != '') {
+            $multipart[] = [
+                'name' => 'in-reply-to',
+                'contents' => $request->input('reply-to'),
+            ];
+        }
+        if ($request->input('mp-syndicate-to')) {
+            foreach ($request->input('mp-syndicate-to') as $syn) {
+                $multipart[] = [
+                    'name' => 'mp-syndicate-to',
+                    'contents' => $syn,
+                ];
+            }
+        }
+        if ($request->input('confirmlocation')) {
+            $latLng = $request->input('location');
+            $geoURL = 'geo:' . str_replace(' ', '', $latLng);
+            $multipart[] = [
+                'name' => 'location',
+                'contents' => $geoURL,
+            ];
+            if ($request->input('address') != '') {
+                $multipart[] = [
+                    'name' => 'place_name',
+                    'contents' => $request->input('address'),
+                ];
+            }
+        }
+        $headers = [
+            'Authorization' => 'Bearer ' . $token,
+        ];
+        try {
+            $response = $this->guzzleClient->post($micropubEndpoint, [
+                'multipart' => $multipart,
+                'headers' => $headers,
+            ]);
+        } catch (\GuzzleHttp\Exception\BadResponseException $e) {
+            return redirect('notes/new')
+                ->withErrors('There was a bad response from the micropub endpoint.', 'endpoint');
+        }
+
+        return $response;
+    }
+
+    /**
+     * Create a new place.
+     *
+     * @param  \Illuminate\Http\Request $request
+     * @return mixed
+     */
+    public function postNewPlace(Request $request)
+    {
+        $domain = $request->session()->get('me');
+        $token = $request->session()->get('token');
+
+        $micropubEndpoint = $this->indieAuthService->discoverMicropubEndpoint($domain, $this->indieClient);
+        if (! $micropubEndpoint) {
+            return (new Response(json_encode([
+                'error' => true,
+                'message' => 'Could not determine the micropub endpoint.',
+            ]), 400))
+            ->header('Content-Type', 'application/json');
+        }
+
+        $place = $this->postPlaceRequest($request, $micropubEndpoint, $token);
+        if ($place === false) {
+            return (new Response(json_encode([
+                'error' => true,
+                'message' => 'Unable to create the new place',
+            ]), 400))
+            ->header('Content-Type', 'application/json');
+        }
+
+        return (new Response(json_encode([
+            'url' => $place,
+            'name' => $request->input('place-name'),
+            'latitude' => $request->input('place-latitude'),
+            'longitude' => $request->input('place-longitude'),
+        ]), 200))
+        ->header('Content-Type', 'application/json');
+    }
+
+    /**
+     * Actually make a micropub request to make a new place.
+     *
+     * @param  \Illuminate\Http\Request $request
+     * @param  string The Micropub endpoint to post to
+     * @param  string The token to authenticate the request with
+     * @param  \GuzzleHttp\Client $client
+     * @return \GuzzleHttp\Response $response | \Illuminate\RedirectFactory redirect
+     */
+    private function postPlaceRequest(
+        Request $request,
+        $micropubEndpoint,
+        $token
+    ) {
+        $formParams = [
+            'h' => 'card',
+            'name' => $request->input('place-name'),
+            'description' => $request->input('place-description'),
+            'geo' => 'geo:' . $request->input('place-latitude') . ',' . $request->input('place-longitude'),
+        ];
+        $headers = [
+            'Authorization' => 'Bearer ' . $token,
+        ];
+        try {
+            $response = $this->guzzleClient->request('POST', $micropubEndpoint, [
+                'form_params' => $formParams,
+                'headers' => $headers,
+            ]);
+        } catch (ClientException $e) {
+            //not sure yet...
+        }
+        if ($response->getStatusCode() == 201) {
+            return $response->getHeader('Location')[0];
+        }
+
+        return false;
+    }
+
+    /**
+     * Make a request to the micropub endpoint requesting any nearby places.
+     *
+     * @param  \Illuminate\Http\Request $request
+     * @param  string $latitude
+     * @param  string $longitude
+     * @return \Illuminate\Http\Response
+     */
+    public function nearbyPlaces(
+        Request $request,
+        $latitude,
+        $longitude
+    ) {
+        $domain = $request->session()->get('me');
+        $token = $request->session()->get('token');
+        $micropubEndpoint = $this->indieAuthService->discoverMicropubEndpoint($domain, $this->indieClient);
+
+        if (! $micropubEndpoint) {
+            return;
+        }
+
+        try {
+            $response = $this->guzzleClient->get($micropubEndpoint, [
+                'headers' => ['Authorization' => 'Bearer ' . $token],
+                'query' => ['q' => 'geo:' . $latitude . ',' . $longitude],
+            ]);
+        } catch (\GuzzleHttp\Exception\BadResponseException $e) {
+            return;
+        }
+
+        return (new Response($response->getBody(), 200))
+                ->header('Content-Type', 'application/json');
+    }
+
+    /**
+     * Parse the syndication targets retreived from a cookie, to a form that can
+     * be used in a view.
+     *
+     * @param  string $syndicationTargets
+     * @return array|null
+     */
+    private function parseSyndicationTargets($syndicationTargets = null)
+    {
+        if ($syndicationTargets === null) {
+            return;
+        }
+        $mpSyndicateTo = [];
+        $parts = explode(';', $syndicationTargets);
+        foreach ($parts as $part) {
+            $target = explode('=', $part);
+            $mpSyndicateTo[] = urldecode($target[1]);
+        }
+        if (count($mpSyndicateTo) > 0) {
+            return $mpSyndicateTo;
+        }
+    }
+}
diff --git a/app/Http/Controllers/MicropubController.php b/app/Http/Controllers/MicropubController.php
new file mode 100644
index 00000000..1a941125
--- /dev/null
+++ b/app/Http/Controllers/MicropubController.php
@@ -0,0 +1,143 @@
+tokenService = $tokenService ?? new TokenService();
+        $this->noteService = $noteService ?? new NoteService();
+        $this->placeService = $placeService ?? new PlaceService();
+    }
+
+    /**
+     * This function receives an API request, verifies the authenticity
+     * then passes over the info to the relavent Service class.
+     *
+     * @param  \Illuminate\Http\Request request
+     * @return \Illuminate\Http\Response
+     */
+    public function post(Request $request)
+    {
+        $httpAuth = $request->header('Authorization');
+        if (preg_match('/Bearer (.+)/', $httpAuth, $match)) {
+            $token = $match[1];
+            $tokenData = $this->tokenService->validateToken($token);
+            if ($tokenData->hasClaim('scope')) {
+                $scopes = explode(' ', $tokenData->getClaim('scope'));
+                if (array_search('post', $scopes) !== false) {
+                    $clientId = $tokenData->getClaim('client_id');
+                    $type = $request->input('h');
+                    if ($type == 'entry') {
+                        $note = $this->noteService->createNote($request, $clientId);
+                        $content = 'Note created at ' . $note->longurl;
+
+                        return (new Response($content, 201))
+                                      ->header('Location', $note->longurl);
+                    }
+                    if ($type == 'card') {
+                        $place = $this->placeService->createPlace($request);
+                        $content = 'Place created at ' . $place->longurl;
+
+                        return (new Response($content, 201))
+                                      ->header('Location', $place->longurl);
+                    }
+                }
+            }
+            $content = http_build_query([
+                'error' => 'invalid_token',
+                'error_description' => 'The token provided is not valid or does not have the necessary scope',
+            ]);
+
+            return (new Response($content, 400))
+                          ->header('Content-Type', 'application/x-www-form-urlencoded');
+        }
+        $content = 'No OAuth token sent with request.';
+
+        return new Response($content, 400);
+    }
+
+    /**
+     * A GET request has been made to `api/post` with an accompanying
+     * token, here we check wether the token is valid and respond
+     * appropriately. Further if the request has the query parameter
+     * synidicate-to we respond with the known syndication endpoints.
+     *
+     * @todo   Move the syndication endpoints into a .env variable
+     * @param  \Illuminate\Http\Request $request
+     * @return \Illuminate\Http\Response
+     */
+    public function getEndpoint(Request $request)
+    {
+        $httpAuth = $request->header('Authorization');
+        if (preg_match('/Bearer (.+)/', $httpAuth, $match)) {
+            $token = $match[1];
+            $valid = $this->tokenService->validateToken($token);
+
+            if ($valid === null) {
+                return new Response('Invalid token', 400);
+            }
+            //we have a valid token, is `syndicate-to` set?
+            if ($request->input('q') === 'syndicate-to') {
+                $content = http_build_query([
+                    'mp-syndicate-to' => 'twitter.com/jonnybarnes',
+                ]);
+
+                return (new Response($content, 200))
+                              ->header('Content-Type', 'application/x-www-form-urlencoded');
+            }
+            //nope, how about a geo URL?
+            if (substr($request->input('q'), 0, 4) === 'geo:') {
+                $geo = explode(':', $request->input('q'));
+                $latlng = explode(',', $geo[1]);
+                $latitude = $latlng[0];
+                $longitude = $latlng[1];
+                $places = Place::near($latitude, $longitude, 1000);
+
+                return (new Response(json_encode($places), 200))
+                        ->header('Content-Type', 'application/json');
+            }
+            //nope, just return the token
+            $content = http_build_query([
+                'me' => $valid->getClaim('me'),
+                'scope' => $valid->getClaim('scope'),
+                'client_id' => $valid->getClaim('client_id'),
+            ]);
+
+            return (new Response($content, 200))
+                          ->header('Content-Type', 'application/x-www-form-urlencoded');
+        }
+        $content = 'No OAuth token sent with request.';
+
+        return new Response($content, 400);
+    }
+}
diff --git a/app/Http/Controllers/NotesAdminController.php b/app/Http/Controllers/NotesAdminController.php
new file mode 100644
index 00000000..6fd48096
--- /dev/null
+++ b/app/Http/Controllers/NotesAdminController.php
@@ -0,0 +1,100 @@
+orderBy('id', 'desc')->get();
+        foreach ($notes as $note) {
+            $note->originalNote = $note->getOriginal('note');
+        }
+
+        return view('admin.listnotes', ['notes' => $notes]);
+    }
+
+    /**
+     * Display the form to edit a specific note.
+     *
+     * @param  string The note id
+     * @return \Illuminate\View\Factory view
+     */
+    public function editNotePage($noteId)
+    {
+        $note = Note::find($noteId);
+        $note->originalNote = $note->getOriginal('note');
+
+        return view('admin.editnote', ['id' => $noteId, 'note' => $note]);
+    }
+
+    /**
+     * Process a request to make a new note.
+     *
+     * @param Illuminate\Http\Request $request
+     * @todo  Sort this mess out
+     */
+    public function createNote(Request $request)
+    {
+        $validator = Validator::make(
+            $request->all(),
+            ['photo' => 'photosize'],
+            ['photosize' => 'At least one uploaded file exceeds size limit of 5MB']
+        );
+        if ($validator->fails()) {
+            return redirect('/admin/note/new')
+                ->withErrors($validator)
+                ->withInput();
+        }
+
+        $note = $this->noteService->createNote($request);
+
+        return view('admin.newnotesuccess', [
+            'id' => $note->id,
+            'shorturl' => $note->shorturl,
+        ]);
+    }
+
+    /**
+     * Process a request to edit a note. Easy since this can only be done
+     * from the admin CP.
+     *
+     * @param  \Illuminate\Http\Request $request
+     * @return \Illuminate\View\Factory view
+     */
+    public function editNote($noteId, Request $request)
+    {
+        //update note data
+        $note = Note::find($noteId);
+        $note->note = $request->input('content');
+        $note->in_reply_to = $request->input('in-reply-to');
+        $note->save();
+
+        if ($request->input('webmentions')) {
+            $wmc = new WebMentionsController();
+            $wmc->send($note);
+        }
+
+        return view('admin.editnotesuccess', ['id' => $noteId]);
+    }
+}
diff --git a/app/Http/Controllers/NotesController.php b/app/Http/Controllers/NotesController.php
new file mode 100644
index 00000000..2a5dcb3f
--- /dev/null
+++ b/app/Http/Controllers/NotesController.php
@@ -0,0 +1,240 @@
+with('webmentions', 'place')->simplePaginate(10);
+        foreach ($notes as $note) {
+            $replies = 0;
+            foreach ($note->webmentions as $webmention) {
+                if ($webmention->type == 'reply') {
+                    $replies = $replies + 1;
+                }
+            }
+            $note->replies = $replies;
+            $note->twitter = $this->checkTwitterReply($note->in_reply_to);
+            $note->iso8601_time = $note->updated_at->toISO8601String();
+            $note->human_time = $note->updated_at->diffForHumans();
+            if ($note->location && ($note->place === null)) {
+                $pieces = explode(':', $note->location);
+                $latlng = explode(',', $pieces[0]);
+                $note->latitude = trim($latlng[0]);
+                $note->longitude = trim($latlng[1]);
+                if (count($pieces) == 2) {
+                    $note->address = $pieces[1];
+                }
+            }
+            if ($note->place !== null) {
+                preg_match('/\((.*)\)/', $note->place->location, $matches);
+                $lnglat = explode(' ', $matches[1]);
+                $note->latitude = $lnglat[1];
+                $note->longitude = $lnglat[0];
+                $note->address = $note->place->name;
+                $note->placeLink = '/places/' . $note->place->slug;
+            }
+            $photoURLs = [];
+            $photos = $note->getMedia();
+            foreach ($photos as $photo) {
+                $photoURLs[] = $photo->getUrl();
+            }
+            $note->photoURLs = $photoURLs;
+        }
+
+        return view('allnotes', ['notes' => $notes]);
+    }
+
+    /**
+     * Show a single note.
+     *
+     * @param  string The id of the note
+     * @return \Illuminate\View\Factory view
+     */
+    public function singleNote($urlId)
+    {
+        $numbers = new Numbers();
+        $realId = $numbers->b60tonum($urlId);
+        $note = Note::find($realId);
+        $replies = [];
+        $reposts = [];
+        $likes = [];
+        foreach ($note->webmentions as $webmention) {
+            switch ($webmention->type) {
+                case 'reply':
+                    $content = unserialize($webmention->content);
+                    $content['source'] = $this->bridgyReply($webmention->source);
+                    $content['photo'] = $this->createPhotoLink($content['photo']);
+                    $content['date'] = $carbon->parse($content['date'])->toDayDateTimeString();
+                    $replies[] = $content;
+                    break;
+
+                case 'repost':
+                    $content = unserialize($webmention->content);
+                    $content['photo'] = $this->createPhotoLink($content['photo']);
+                    $content['date'] = $carbon->parse($content['date'])->toDayDateTimeString();
+                    $reposts[] = $content;
+                    break;
+
+                case 'like':
+                    $content = unserialize($webmention->content);
+                    $content['photo'] = $this->createPhotoLink($content['photo']);
+                    $likes[] = $content;
+                    break;
+            }
+        }
+        $note->twitter = $this->checkTwitterReply($note->in_reply_to);
+        $note->iso8601_time = $note->updated_at->toISO8601String();
+        $note->human_time = $note->updated_at->diffForHumans();
+        if ($note->location && ($note->place === null)) {
+            $pieces = explode(':', $note->location);
+            $latlng = explode(',', $pieces[0]);
+            $note->latitude = trim($latlng[0]);
+            $note->longitude = trim($latlng[1]);
+            if (count($pieces) == 2) {
+                $note->address = $pieces[1];
+            }
+        }
+        if ($note->place !== null) {
+            preg_match('/\((.*)\)/', $note->place->location, $matches);
+            $lnglat = explode(' ', $matches[1]);
+            $note->latitude = $lnglat[1];
+            $note->longitude = $lnglat[0];
+            $note->address = $note->place->name;
+            $note->placeLink = '/places/' . $note->place->slug;
+        }
+
+        $note->photoURLs = [];
+        foreach ($note->getMedia() as $photo) {
+            $note->photoURLs[] = $photo->getUrl();
+        }
+
+        return view('singlenote', [
+            'note' => $note,
+            'replies' => $replies,
+            'reposts' => $reposts,
+            'likes' => $likes,
+        ]);
+    }
+
+    /**
+     * Redirect /note/{decID} to /notes/{nb60id}.
+     *
+     * @param  string The decimal id of he note
+     * @return \Illuminate\Routing\RedirectResponse redirect
+     */
+    public function singleNoteRedirect($decId)
+    {
+        $numbers = new Numbers();
+        $realId = $numbers->numto60($decId);
+
+        $url = config('app.url') . '/notes/' . $realId;
+
+        return redirect($url);
+    }
+
+    /**
+     * Show all notes tagged with {tag}.
+     *
+     * @param  string The tag
+     * @return \Illuminate\View\Factory view
+     */
+    public function taggedNotes($tag)
+    {
+        $tagId = Tag::where('tag', $tag)->pluck('id');
+        $notes = Tag::find($tagId)->notes()->orderBy('updated_at', 'desc')->get();
+        foreach ($notes as $note) {
+            $note->iso8601_time = $note->updated_at->toISO8601String();
+            $note->human_time = $note->updated_at->diffForHumans();
+        }
+
+        return view('taggednotes', ['notes' => $notes, 'tag' => $tag]);
+    }
+
+    /**
+     * Swap a brid.gy URL shim-ing a twitter reply to a real twitter link.
+     *
+     * @param  string
+     * @return string
+     */
+    public function bridgyReply($source)
+    {
+        $url = $source;
+        if (mb_substr($source, 0, 28, 'UTF-8') == 'https://brid-gy.appspot.com/') {
+            $parts = explode('/', $source);
+            $tweetId = array_pop($parts);
+            if ($tweetId) {
+                $url = 'https://twitter.com/_/status/' . $tweetId;
+            }
+        }
+
+        return $url;
+    }
+
+    /**
+     * Create the photo link.
+     *
+     * @param  string
+     * @return string
+     */
+    public function createPhotoLink($url)
+    {
+        $host = parse_url($url)['host'];
+        if ($host != 'twitter.com' && $host != 'pbs.twimg.com') {
+            return '/assets/profile-images/' . $host . '/image';
+        }
+        if (mb_substr($url, 0, 20) == 'http://pbs.twimg.com') {
+            return str_replace('http://', 'https://', $url);
+        }
+    }
+
+    /**
+     * Twitter!!!
+     *
+     * @param  string  The reply to URL
+     * @return string | null
+     */
+    private function checkTwitterReply($url)
+    {
+        if ($url == null) {
+            return;
+        }
+
+        if (mb_substr($url, 0, 20, 'UTF-8') !== 'https://twitter.com/') {
+            return;
+        }
+
+        $arr = explode('/', $url);
+        $tweetId = end($arr);
+        if (Cache::has($tweetId)) {
+            return Cache::get($tweetId);
+        }
+        try {
+            $oEmbed = Twitter::getOembed([
+                'id' => $tweetId,
+                'align' => 'center',
+                'omit_script' => true,
+                'maxwidth' => 550,
+            ]);
+        } catch (\Exception $e) {
+            return;
+        }
+        Cache::put($tweetId, $oEmbed, ($oEmbed->cache_age / 60));
+
+        return $oEmbed;
+    }
+}
diff --git a/app/Http/Controllers/PhotosController.php b/app/Http/Controllers/PhotosController.php
new file mode 100644
index 00000000..9eaeb769
--- /dev/null
+++ b/app/Http/Controllers/PhotosController.php
@@ -0,0 +1,94 @@
+imageResizeLimit = 800;
+    }
+
+    /**
+     * Save an uploaded photo to the image folder.
+     *
+     * @param  \Illuminate\Http\Request  $request
+     * @param  string  The associated note’s nb60 ID
+     * @return bool
+     */
+    public function saveImage(Request $request, $nb60id)
+    {
+        if ($request->hasFile('photo') !== true) {
+            return false;
+        }
+        $photoFilename = 'note-' . $nb60id;
+        $path = public_path() . '/assets/img/notes/';
+        $ext = $request->file('photo')->getClientOriginalExtension();
+        $photoFilename .= '.' . $ext;
+        $request->file('photo')->move($path, $photoFilename);
+
+        return true;
+    }
+
+    /**
+     * Prepare a photo for posting to twitter.
+     *
+     * @param  string  photo fileanme
+     * @return string  small photo filename, or null
+     */
+    public function makeSmallPhotoForTwitter($photoFilename)
+    {
+        $imagine = new Imagine();
+        $orig = $imagine->open(public_path() . '/assets/img/notes/' . $photoFilename);
+        $size = [$orig->getSize()->getWidth(), $orig->getSize()->getHeight()];
+        if ($size[0] > $this->imageResizeLimit || $size[1] > $this->imageResizeLimit) {
+            $filenameParts = explode('.', $photoFilename);
+            $preExt = count($filenameParts) - 2;
+            $filenameParts[$preExt] .= '-small';
+            $photoFilenameSmall = implode('.', $filenameParts);
+            $aspectRatio = $size[0] / $size[1];
+            $box = ($aspectRatio >= 1) ?
+                [$this->imageResizeLimit, (int) round($this->imageResizeLimit / $aspectRatio)]
+                :
+                [(int) round($this->imageResizeLimit * $aspectRatio), $this->imageResizeLimit];
+            $orig->resize(new Box($box[0], $box[1]))
+                 ->save(public_path() . '/assets/img/notes/' . $photoFilenameSmall);
+
+            return $photoFilenameSmall;
+        }
+    }
+
+    /**
+     * Get the image path for a note.
+     *
+     * @param  string $nb60id
+     * @return string | null
+     */
+    public function getPhotoPath($nb60id)
+    {
+        $filesystem = new Filesystem();
+        $photoDir = public_path() . '/assets/img/notes';
+        $files = $filesystem->files($photoDir);
+        foreach ($files as $file) {
+            $parts = explode('.', $file);
+            $name = $parts[0];
+            $dirs = explode('/', $name);
+            $actualname = last($dirs);
+            if ($actualname == 'note-' . $nb60id) {
+                $ext = $parts[1];
+            }
+        }
+        if (isset($ext)) {
+            return '/assets/img/notes/note-' . $nb60id . '.' . $ext;
+        }
+    }
+}
diff --git a/app/Http/Controllers/PlacesAdminController.php b/app/Http/Controllers/PlacesAdminController.php
new file mode 100644
index 00000000..24f492f9
--- /dev/null
+++ b/app/Http/Controllers/PlacesAdminController.php
@@ -0,0 +1,85 @@
+ $places]);
+    }
+
+    /**
+     * Show the form to make a new place.
+     *
+     * @return \Illuminate\View\Factory view
+     */
+    public function newPlacePage()
+    {
+        return view('admin.newplace');
+    }
+
+    /**
+     * Display the form to edit a specific place.
+     *
+     * @param  string The place id
+     * @return \Illuminate\View\Factory view
+     */
+    public function editPlacePage($placeId)
+    {
+        $place = Place::findOrFail($placeId);
+
+        $latitude = $place->getLatitude();
+        $longitude = $place->getLongitude();
+
+        return view('admin.editplace', [
+            'id' => $placeId,
+            'name' => $place->name,
+            'description' => $place->description,
+            'latitude' => $latitude,
+            'longitude' => $longitude,
+        ]);
+    }
+
+    /**
+     * Process a request to make a new place.
+     *
+     * @param  Illuminate\Http\Request $request
+     * @return Illuminate\View\Factory view
+     */
+    public function createPlace(Request $request)
+    {
+        $this->placeService->createPlace($request);
+
+        return view('admin.newplacesuccess');
+    }
+
+    /**
+     * Process a request to edit a place.
+     *
+     * @param string The place id
+     * @param Illuminate\Http\Request $request
+     * @return Illuminate\View\Factory view
+     */
+    public function editPlace($placeId, Request $request)
+    {
+        $place = Place::findOrFail($placeId);
+        $place->name = $request->name;
+        $place->description = $request->description;
+        $place->location = new Point((float) $request->latitude, (float) $request->longitude);
+        $place->save();
+
+        return view('admin.editplacesuccess');
+    }
+}
diff --git a/app/Http/Controllers/PlacesController.php b/app/Http/Controllers/PlacesController.php
new file mode 100644
index 00000000..d28a6852
--- /dev/null
+++ b/app/Http/Controllers/PlacesController.php
@@ -0,0 +1,89 @@
+ $places]);
+    }
+
+    /**
+     * Show the form for creating a new resource.
+     *
+     * @return \Illuminate\Http\Response
+     */
+    public function create()
+    {
+        //
+    }
+
+    /**
+     * Store a newly created resource in storage.
+     *
+     * @param  \Illuminate\Http\Request  $request
+     * @return \Illuminate\Http\Response
+     */
+    public function store(Request $request)
+    {
+        //
+    }
+
+    /**
+     * Display the specified resource.
+     *
+     * @param  string  $slug
+     * @return \Illuminate\Http\Response
+     */
+    public function show($slug)
+    {
+        $place = Place::where('slug', '=', $slug)->first();
+
+        return view('singleplace', ['place' => $place]);
+    }
+
+    /**
+     * Show the form for editing the specified resource.
+     *
+     * @param  int  $id
+     * @return \Illuminate\Http\Response
+     */
+    public function edit($id)
+    {
+        //
+    }
+
+    /**
+     * Update the specified resource in storage.
+     *
+     * @param  \Illuminate\Http\Request  $request
+     * @param  int  $id
+     * @return \Illuminate\Http\Response
+     */
+    public function update(Request $request, $id)
+    {
+        //
+    }
+
+    /**
+     * Remove the specified resource from storage.
+     *
+     * @param  int  $id
+     * @return \Illuminate\Http\Response
+     */
+    public function destroy($id)
+    {
+        //
+    }
+}
diff --git a/app/Http/Controllers/ShortURLsController.php b/app/Http/Controllers/ShortURLsController.php
new file mode 100644
index 00000000..9c34ca0f
--- /dev/null
+++ b/app/Http/Controllers/ShortURLsController.php
@@ -0,0 +1,120 @@
+b60tonum($shortURLId);
+        $shorturl = ShortURL::find($num);
+        $redirect = $shorturl->redirect;
+
+        return redirect($redirect);
+    }
+
+    /**
+     * I had an old redirect systme breifly, but cool URLs should still work.
+     *
+     * @param  string URL ID
+     * @return \Illuminate\Routing\Redirector redirect
+     */
+    public function oldRedirect($shortURLId)
+    {
+        $filename = base_path() . '/public/assets/old-shorturls.json';
+        $handle = fopen($filename, 'r');
+        $contents = fread($handle, filesize($filename));
+        $object = json_decode($contents);
+
+        foreach ($object as $key => $val) {
+            if ($shortURLId == $key) {
+                return redirect($val);
+            }
+        }
+
+        return 'This id was never used.
+        Old redirects are located at
+        
+            old-shorturls.json
+        .';
+    }
+}
diff --git a/app/Http/Controllers/TokensController.php b/app/Http/Controllers/TokensController.php
new file mode 100644
index 00000000..5f896e80
--- /dev/null
+++ b/app/Http/Controllers/TokensController.php
@@ -0,0 +1,61 @@
+tokenService = $tokenService ?? new TokenService();
+    }
+
+    /**
+     * Show all the saved tokens.
+     *
+     * @return \Illuminate\View\Factory view
+     */
+    public function showTokens()
+    {
+        $tokens = $$his->tokenService->getAll();
+
+        return view('admin.listtokens', ['tokens' => $tokens]);
+    }
+
+    /**
+     * Show the form to delete a certain token.
+     *
+     * @param  string The token id
+     * @return \Illuminate\View\Factory view
+     */
+    public function deleteToken($tokenId)
+    {
+        return view('admin.deletetoken', ['id' => $tokenId]);
+    }
+
+    /**
+     * Process the request to delete a token.
+     *
+     * @param  string The token id
+     * @return \Illuminate\View\Factory view
+     */
+    public function postDeleteToken($tokenId)
+    {
+        $this->tokenService->deleteToken($tokenId);
+
+        return view('admin.deletetokensuccess', ['id' => $tokenId]);
+    }
+}
diff --git a/app/Http/Controllers/WebMentionsController.php b/app/Http/Controllers/WebMentionsController.php
new file mode 100644
index 00000000..ccffd451
--- /dev/null
+++ b/app/Http/Controllers/WebMentionsController.php
@@ -0,0 +1,100 @@
+has('target') !== true) || ($request->has('source') !== true)) {
+            return new Response(
+                'You need both the target and source parameters',
+                400
+            );
+        }
+
+        //next check the $target is valid
+        $path = parse_url($request->input('target'))['path'];
+        $pathParts = explode('/', $path);
+
+        switch ($pathParts[1]) {
+            case 'notes':
+                //we have a note
+                $noteId = $pathParts[2];
+                $numbers = new Numbers();
+                $realId = $numbers->b60tonum($noteId);
+                try {
+                    $note = Note::findOrFail($realId);
+                    $this->dispatch(new ProcessWebMention($note, $request->input('source')));
+                } catch (ModelNotFoundException $e) {
+                    return new Response('This note doesn’t exist.', 400);
+                }
+
+                return new Response(
+                    'Webmention received, it will be processed shortly',
+                    202
+                );
+                break;
+            case 'blog':
+                return new Response(
+                    'I don’t accept webmentions for blog posts yet.',
+                    501
+                );
+                break;
+            default:
+                return new Response(
+                    'Invalid request',
+                    400
+                );
+                break;
+        }
+    }
+
+    /**
+     * Send a webmention.
+     *
+     * @param  \App\Note  $note
+     * @return array   An array of successful then failed URLs
+     */
+    public function send(Note $note)
+    {
+        //grab the URLs
+        $urlsInReplyTo = explode(' ', $note->in_reply_to);
+        $urlsNote = $this->getLinks($note->note);
+        $urls = array_filter(array_merge($urlsInReplyTo, $urlsNote)); //filter out none URLs
+        foreach ($urls as $url) {
+            $this->dispatch(new SendWebMentions($url, $note->longurl));
+        }
+    }
+
+    /**
+     * Get the URLs from a note.
+     */
+    private function getLinks($html)
+    {
+        $urls = [];
+        $dom = new \DOMDocument();
+        $dom->loadHTML($html);
+        $anchors = $dom->getElementsByTagName('a');
+        foreach ($anchors as $anchor) {
+            $urls[] = ($anchor->hasAttribute('href')) ? $anchor->getAttribute('href') : false;
+        }
+
+        return $urls;
+    }
+}
diff --git a/app/Http/Kernel.php b/app/Http/Kernel.php
new file mode 100644
index 00000000..ebad4d16
--- /dev/null
+++ b/app/Http/Kernel.php
@@ -0,0 +1,55 @@
+ [
+            \App\Http\Middleware\EncryptCookies::class,
+            \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
+            \Illuminate\Session\Middleware\StartSession::class,
+            \Illuminate\View\Middleware\ShareErrorsFromSession::class,
+            \App\Http\Middleware\VerifyCsrfToken::class,
+            \App\Http\Middleware\LinkHeadersMiddleware::class,
+        ],
+
+        'api' => [
+            'throttle:60,1',
+        ],
+    ];
+
+    /**
+     * The application's route middleware.
+     *
+     * These middleware may be assigned to groups or used individually.
+     *
+     * @var array
+     */
+    protected $routeMiddleware = [
+        'auth' => \App\Http\Middleware\Authenticate::class,
+        'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
+        'can' => \Illuminate\Foundation\Http\Middleware\Authorize::class,
+        'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class,
+        'myauth' => \App\Http\Middleware\MyAuthMiddleware::class,
+        'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
+    ];
+}
diff --git a/app/Http/Middleware/Authenticate.php b/app/Http/Middleware/Authenticate.php
new file mode 100644
index 00000000..67abcaea
--- /dev/null
+++ b/app/Http/Middleware/Authenticate.php
@@ -0,0 +1,30 @@
+guest()) {
+            if ($request->ajax() || $request->wantsJson()) {
+                return response('Unauthorized.', 401);
+            } else {
+                return redirect()->guest('login');
+            }
+        }
+
+        return $next($request);
+    }
+}
diff --git a/app/Http/Middleware/EncryptCookies.php b/app/Http/Middleware/EncryptCookies.php
new file mode 100644
index 00000000..3aa15f8d
--- /dev/null
+++ b/app/Http/Middleware/EncryptCookies.php
@@ -0,0 +1,17 @@
+header('Link', '; rel="authorization_endpoint"', false);
+        $response->header('Link', config('app.url') . '/api/token>; rel="token_endpoint"', false);
+        $response->header('Link', config('app.url') . '/api/post>; rel="micropub"', false);
+        $response->header('Link', config('app.url') . '/webmention>; rel="webmention"', false);
+
+        return $response;
+    }
+}
diff --git a/app/Http/Middleware/MyAuthMiddleware.php b/app/Http/Middleware/MyAuthMiddleware.php
new file mode 100644
index 00000000..5354e55b
--- /dev/null
+++ b/app/Http/Middleware/MyAuthMiddleware.php
@@ -0,0 +1,25 @@
+session()->has('loggedin') !== true) {
+            //they’re not logged in, so send them to login form
+            return redirect()->route('login');
+        }
+
+        return $next($request);
+    }
+}
diff --git a/app/Http/Middleware/RedirectIfAuthenticated.php b/app/Http/Middleware/RedirectIfAuthenticated.php
new file mode 100644
index 00000000..e27860e2
--- /dev/null
+++ b/app/Http/Middleware/RedirectIfAuthenticated.php
@@ -0,0 +1,26 @@
+check()) {
+            return redirect('/');
+        }
+
+        return $next($request);
+    }
+}
diff --git a/app/Http/Middleware/VerifyCsrfToken.php b/app/Http/Middleware/VerifyCsrfToken.php
new file mode 100644
index 00000000..c8545185
--- /dev/null
+++ b/app/Http/Middleware/VerifyCsrfToken.php
@@ -0,0 +1,20 @@
+ config('url.longurl')], function () {
+    //Static homepage
+    Route::get('/', function () {
+        return view('homepage');
+    });
+
+    //Static project page
+    Route::get('projects', function () {
+        return view('projects');
+    });
+
+    //The login routes to get authe'd for admin
+    Route::get('login', ['as' => 'login', function () {
+        return view('login');
+    }]);
+    Route::post('login', 'AuthController@login');
+
+    //Admin pages grouped for filter
+    Route::group(['middleware' => 'myauth'], function () {
+        Route::get('admin', 'AdminController@showWelcome');
+
+        //Articles
+        Route::get('admin/blog/new', 'ArticlesAdminController@newArticle');
+        Route::get('admin/blog/edit', 'ArticlesAdminController@listArticles');
+        Route::get('admin/blog/edit/{id}', 'ArticlesAdminController@editArticle');
+        Route::get('admin/blog/delete/{id}', 'ArticlesAdminController@deleteArticle');
+        Route::post('admin/blog/new', 'ArticlesAdminController@postNewArticle');
+        Route::post('admin/blog/edit/{id}', 'ArticlesAdminController@postEditArticle');
+        Route::post('admin/blog/delete/{id}', 'ArticlesAdminController@postDeleteArticle');
+
+        //Notes
+        Route::get('admin/note/new', 'NotesAdminController@newNotePage');
+        Route::get('admin/note/edit', 'NotesAdminController@listNotesPage');
+        Route::get('admin/note/edit/{id}', 'NotesAdminController@editNotePage');
+        Route::post('admin/note/new', 'NotesAdminController@createNote');
+        Route::post('admin/note/edit/{id}', 'NotesAdminController@editNote');
+
+        //Tokens
+        Route::get('admin/tokens', 'TokensController@showTokens');
+        Route::get('admin/tokens/delete/{id}', 'TokensController@deleteToken');
+        Route::post('admin/tokens/delete/{id}', 'TokensController@postDeleteToken');
+
+        //Micropub Clients
+        Route::get('admin/clients', 'ClientsAdminController@listClients');
+        Route::get('admin/clients/new', 'ClientsAdminController@newClient');
+        Route::get('admin/clients/edit/{id}', 'ClientsAdminController@editClient');
+        Route::post('admin/clients/new', 'ClientsAdminController@postNewClient');
+        Route::post('admin/clients/edit/{id}', 'ClientsAdminController@postEditClient');
+
+        //Contacts
+        Route::get('admin/contacts/new', 'ContactsAdminController@newContact');
+        Route::get('admin/contacts/edit', 'ContactsAdminController@listContacts');
+        Route::get('admin/contacts/edit/{id}', 'ContactsAdminController@editContact');
+        Route::get('admin/contacts/edit/{id}/getavatar', 'ContactsAdminController@getAvatar');
+        Route::get('admin/contacts/delete/{id}', 'ContactsAdminController@deleteContact');
+        Route::post('admin/contacts/new', 'ContactsAdminController@postNewContact');
+        Route::post('admin/contacts/edit/{id}', 'ContactsAdminController@postEditContact');
+        Route::post('admin/contacts/delete/{id}', 'ContactsAdminController@postDeleteContact');
+
+        //Places
+        Route::get('admin/places/new', 'PlacesAdminController@newPlacePage');
+        Route::get('admin/places/edit', 'PlacesAdminController@listPlacesPage');
+        Route::get('admin/places/edit/{id}', 'PlacesAdminController@editPlacePage');
+        Route::post('admin/places/new', 'PlacesAdminController@createPlace');
+        Route::post('admin/places/edit/{id}', 'PlacesAdminController@editPlace');
+    });
+
+    //Blog pages using ArticlesController
+    Route::get('blog/s/{id}', 'ArticlesController@onlyIdInURL');
+    Route::get('blog/{year?}/{month?}', 'ArticlesController@showAllArticles');
+    Route::get('blog/{year}/{month}/{slug}', 'ArticlesController@singleArticle');
+
+    //micropub new notes page
+    //this needs to be first so `notes/new` doesn't match `notes/{id}`
+    Route::get('notes/new', 'MicropubClientController@newNotePage');
+    Route::post('notes/new', 'MicropubClientController@postNewNote');
+
+    //Notes pages using NotesController
+    Route::get('notes', 'NotesController@showNotes');
+    Route::get('note/{id}', 'NotesController@singleNoteRedirect');
+    Route::get('notes/{id}', 'NotesController@singleNote');
+    Route::get('notes/tagged/{tag}', 'NotesController@taggedNotes');
+
+    //indieauth
+    Route::any('beginauth', 'IndieAuthController@beginauth');
+    Route::get('indieauth', 'IndieAuthController@indieauth');
+    Route::post('api/token', 'IndieAuthController@tokenEndpoint');
+    Route::get('logout', 'IndieAuthController@indieauthLogout');
+
+    //micropub endoints
+    Route::post('api/post', 'MicropubController@post');
+    Route::get('api/post', 'MicropubController@getEndpoint');
+
+    //micropub refresh syndication targets
+    Route::get('refresh-syndication-targets', 'MicropubClientController@refreshSyndicationTargets');
+
+    //webmention
+    Route::get('webmention', function () {
+        return view('webmention-endpoint');
+    });
+    Route::post('webmention', 'WebMentionsController@receive');
+
+    //Contacts
+    Route::get('contacts', 'ContactsController@showAll');
+    Route::get('contacts/{nick}', 'ContactsController@showSingle');
+
+    //Places
+    Route::get('places', 'PlacesController@index');
+    Route::get('places/{slug}', 'PlacesController@show');
+    //Places micropub
+    Route::get('places/near/{lat}/{lng}', 'MicropubClientController@nearbyPlaces');
+    Route::post('places/new', 'MicropubClientController@postNewPlace');
+
+    Route::get('feed', 'ArticlesController@makeRSS');
+});
+
+//Short URL
+Route::group(['domain' => config('url.shorturl')], function () {
+    Route::get('/', 'ShortURLsController@baseURL');
+    Route::get('@', 'ShortURLsController@twitter');
+    Route::get('+', 'ShortURLsController@googlePlus');
+    Route::get('α', 'ShortURLsController@appNet');
+
+    Route::get('{type}/{id}', 'ShortURLsController@expandType')->where(
+        [
+            'type' => '[bt]',
+            'id' => '[0-9A-HJ-NP-Z_a-km-z]+',
+        ]
+    );
+
+    Route::get('h/{id}', 'ShortURLsController@redirect');
+    Route::get('{id}', 'ShortURLsController@oldRedirect')->where(
+        [
+            'id' => '[0-9A-HJ-NP-Z_a-km-z]{4}',
+        ]
+    );
+});
diff --git a/app/Jobs/Job.php b/app/Jobs/Job.php
new file mode 100644
index 00000000..55ece29a
--- /dev/null
+++ b/app/Jobs/Job.php
@@ -0,0 +1,21 @@
+note = $note;
+        $this->source = $source;
+    }
+
+    /**
+     * Execute the job.
+     *
+     * @param  \Jonnybarnes\WebmentionsParser\Parser $parser
+     * @return void
+     */
+    public function handle(Parser $parser)
+    {
+        $sourceURL = parse_url($this->source);
+        $baseURL = $sourceURL['scheme'] . '://' . $sourceURL['host'];
+        $remoteContent = $this->getRemoteContent($this->source);
+        $microformats = $this->parseHTML($remoteContent, $baseURL);
+        $count = WebMention::where('source', '=', $this->source)->count();
+        if ($count > 0) {
+            //we already have a webmention from this source
+            $webmentions = WebMention::where('source', '=', $this->source)->get();
+            foreach ($webmentions as $webmention) {
+                //now check it still 'mentions' this target
+                //we switch for each type of mention (reply/like/repost)
+                switch ($webmention->type) {
+                    case 'reply':
+                        if ($parser->checkInReplyTo($microformats, $note->longurl) == false) {
+                            //it doesn't so delete
+                            $webmention->delete();
+
+                            return true;
+                        }
+                        //webmenion is still a reply, so update content
+                        $content = $parser->replyContent($microformats);
+                        $this->saveImage($content);
+                        $content['reply'] = $this->filterHTML($content['reply']);
+                        $content = serialize($content);
+                        $webmention->content = $content;
+                        $webmention->save();
+
+                        return true;
+                        break;
+                    case 'like':
+                        if ($parser->checkLikeOf($microformats, $note->longurl) == false) {
+                            //it doesn't so delete
+                            $webmention->delete();
+
+                            return true;
+                        } //note we don't need to do anything if it still is a like
+                        break;
+                    case 'repost':
+                        if ($parser->checkRepostOf($microformats, $note->longurl) == false) {
+                            //it doesn't so delete
+                            $webmention->delete();
+
+                            return true;
+                        } //again, we don't need to do anything if it still is a repost
+                        break;
+                }//switch
+            }//foreach
+        }//if
+        //no wemention in db so create new one
+        $webmention = new WebMention();
+        //check it is in fact a reply
+        if ($parser->checkInReplyTo($microformats, $note->longurl)) {
+            $content = $parser->replyContent($microformats);
+            $this->saveImage($content);
+            $content['reply'] = $this->filterHTML($content['reply']);
+            $content = serialize($content);
+            $webmention->source = $this->source;
+            $webmention->target = $note->longurl;
+            $webmention->commentable_id = $this->note->id;
+            $webmention->commentable_type = 'App\Note';
+            $webmention->type = 'reply';
+            $webmention->content = $content;
+            $webmention->save();
+
+            return true;
+        } elseif ($parser->checkLikeOf($microformats, $note->longurl)) {
+            //it is a like
+            $content = $parser->likeContent($microformats);
+            $this->saveImage($content);
+            $content = serialize($content);
+            $webmention->source = $this->source;
+            $webmention->target = $note->longurl;
+            $webmention->commentable_id = $this->note->id;
+            $webmention->commentable_type = 'App\Note';
+            $webmention->type = 'like';
+            $webmention->content = $content;
+            $webmention->save();
+
+            return true;
+        } elseif ($parser->checkRepostOf($microformats, $note->longurl)) {
+            //it is a repost
+            $content = $parser->repostContent($microformats);
+            $this->saveImage($content);
+            $content = serialize($content);
+            $webmention->source = $this->source;
+            $webmention->target = $note->longurl;
+            $webmention->commentable_id = $this->note->id;
+            $webmention->commentable_type = 'App\Note';
+            $webmention->type = 'repost';
+            $webmention->content = $content;
+            $webmention->save();
+
+            return true;
+        }
+    }
+
+    /**
+     * Retreive the remote content from a URL, and caches the result.
+     *
+     * @param  string  The URL to retreive content from
+     * @return string  The HTML from the URL
+     */
+    private function getRemoteContent($url)
+    {
+        $client = new Client();
+
+        $response = $client->get($url);
+        $html = (string) $response->getBody();
+        $path = storage_path() . '/HTML/' . $this->createFilenameFromURL($url);
+        $this->fileForceContents($path, $html);
+
+        return $html;
+    }
+
+    /**
+     * Create a file path from a URL. This is used when caching the HTML
+     * response.
+     *
+     * @param  string  The URL
+     * @return string  The path name
+     */
+    private function createFilenameFromURL($url)
+    {
+        $url = str_replace(['https://', 'http://'], ['', ''], $url);
+        if (substr($url, -1) == '/') {
+            $url = $url . 'index.html';
+        }
+
+        return $url;
+    }
+
+    /**
+     * Save a file, and create any necessary folders.
+     *
+     * @param string  The directory to save to
+     * @param binary  The file to save
+     */
+    private function fileForceContents($dir, $contents)
+    {
+        $parts = explode('/', $dir);
+        $name = array_pop($parts);
+        $dir = implode('/', $parts);
+        if (! is_dir($dir)) {
+            mkdir($dir, 0755, true);
+        }
+        file_put_contents("$dir/$name", $contents);
+    }
+
+    /**
+     * A wrapper function for php-mf2’s parse method.
+     *
+     * @param  string  The HTML to parse
+     * @param  string  The base URL to resolve relative URLs in the HTML against
+     * @return array   The porcessed microformats
+     */
+    private function parseHTML($html, $baseurl)
+    {
+        $microformats = \Mf2\parse((string) $html, $baseurl);
+
+        return $microformats;
+    }
+
+    /**
+     * Save a profile image to the local cache.
+     *
+     * @param  array  source content
+     * @return bool   wether image was saved or not (we don’t save twitter profiles)
+     */
+    public function saveImage(array $content)
+    {
+        $photo = $content['photo'];
+        $home = $content['url'];
+        //dont save pbs.twimg.com links
+        if (parse_url($photo)['host'] != 'pbs.twimg.com'
+              && parse_url($photo)['host'] != 'twitter.com') {
+            $client = new Client();
+            try {
+                $response = $client->get($photo);
+                $image = $response->getBody(true);
+                $path = public_path() . '/assets/profile-images/' . parse_url($home)['host'] . '/image';
+                $this->fileForceContents($path, $image);
+            } catch (Exception $e) {
+                // we are openning and reading the default image so that
+                // fileForceContent work
+                $default = public_path() . '/assets/profile-images/default-image';
+                $handle = fopen($default, 'rb');
+                $image = fread($handle, filesize($default));
+                fclose($handle);
+                $path = public_path() . '/assets/profile-images/' . parse_url($home)['host'] . '/image';
+                $this->fileForceContents($path, $image);
+            }
+
+            return true;
+        }
+
+        return false;
+    }
+
+    /**
+     * Purify HTML received from a webmention.
+     *
+     * @param  string  The HTML to be processed
+     * @return string  The processed HTML
+     */
+    public function filterHTML($html)
+    {
+        $config = HTMLPurifier_Config::createDefault();
+        $config->set('Cache.SerializerPath', storage_path() . '/HTMLPurifier');
+        $purifier = new HTMLPurifier($config);
+
+        return $purifier->purify($html);
+    }
+}
diff --git a/app/Jobs/SendWebMentions.php b/app/Jobs/SendWebMentions.php
new file mode 100644
index 00000000..796fc97c
--- /dev/null
+++ b/app/Jobs/SendWebMentions.php
@@ -0,0 +1,86 @@
+url = $url;
+        $this->source = $source;
+    }
+
+    /**
+     * Execute the job.
+     *
+     * @return void
+     */
+    public function handle(Client $client)
+    {
+        $endpoint = $this->discoverWebmentionEndpoint($this->url, $client);
+        if ($endpoint) {
+            $client->post($endpoint, [
+                'form_params' => [
+                    'source' => $this->source,
+                    'target' => $this->url,
+                ],
+            ]);
+        }
+    }
+
+    /**
+     * Discover if a URL has a webmention endpoint.
+     *
+     * @param  string  The URL
+     * @param  \GuzzleHttp\Client $client
+     * @return string  The webmention endpoint URL
+     */
+    private function discoverWebmentionEndpoint($url, $client)
+    {
+        $endpoint = null;
+
+        $response = $client->get($url);
+        //check HTTP Headers for webmention endpoint
+        $links = \GuzzleHttp\Psr7\parse_header($response->getHeader('Link'));
+        foreach ($links as $link) {
+            if ($link['rel'] == 'webmention') {
+                return trim($link[0], '<>');
+            }
+        }
+
+        //failed to find a header so parse HTML
+        $html = (string) $response->getBody();
+
+        $mf2 = new \Mf2\Parser($html, $url);
+        $rels = $mf2->parseRelsAndAlternates();
+        if (array_key_exists('webmention', $rels[0])) {
+            $endpoint = $rels[0]['webmention'][0];
+        } elseif (array_key_exists('http://webmention.org/', $rels[0])) {
+            $endpoint = $rels[0]['http://webmention.org/'][0];
+        }
+        if ($endpoint) {
+            if (filter_var($endpoint, FILTER_VALIDATE_URL)) {
+                return $endpoint;
+            }
+            //it must be a relative url, so resolve with php-mf2
+            return $mf2->resolveUrl($endpoint);
+        }
+
+        return false;
+    }
+}
diff --git a/app/Jobs/SyndicateToTwitter.php b/app/Jobs/SyndicateToTwitter.php
new file mode 100644
index 00000000..377932f7
--- /dev/null
+++ b/app/Jobs/SyndicateToTwitter.php
@@ -0,0 +1,108 @@
+note = $note;
+    }
+
+    /**
+     * Execute the job.
+     *
+     * @param  \Jonnybarnes\IndieWeb\Numbers $numbers
+     * @param  \Jonnybarnes\IndieWeb\NotePrep $noteprep
+     * @return void
+     */
+    public function handle(Numbers $numbers, NotePrep $noteprep)
+    {
+        $noteSwappedNames = $this->swapNames($this->note->getOriginal('note'));
+        $shorturl = 'https://' . config('url.shorturl') . '/t/' . $numbers->numto60($this->note->id);
+        $tweet = $noteprep->createNote($noteSwappedNames, $shorturl, 140, true);
+        $tweetOpts = ['status' => $tweet, 'format' => 'json'];
+        if ($this->note->in_reply_to) {
+            $tweetOpts['in_reply_to_status_id'] = $noteprep->replyTweetId($this->note->in_reply_to);
+        }
+
+        /*if ($this->note->location) {
+            $explode = explode(':', $this->note->location);
+            $location = (count($explode) == 2) ? explode(',', $explode[0]) : explode(',', $explode);
+            $lat = trim($location[0]);
+            $long = trim($location[1]);
+            $jsonPlaceId = Twitter::getGeoReverse(array('lat' => $lat, 'long' => $long, 'format' => 'json'));
+            $parsePlaceId = json_decode($jsonPlaceId);
+            $placeId = $parsePlaceId->result->places[0]->id ?: null;
+            $tweetOpts['lat'] = $lat;
+            $tweetOpts['long'] = $long;
+            if ($placeId) {
+                $tweetOpts['place_id'] = $placeId;
+            }
+        }*/
+
+        $mediaItems = $this->note->getMedia();
+        if (count($mediaItems) > 0) {
+            foreach ($mediaItems as $item) {
+                $uploadedMedia = Twitter::uploadMedia(['media' => file_get_contents($item->getUrl())]);
+                $mediaIds[] = $uploadedMedia->media_id_string;
+            }
+            $tweetOpts['media_ids'] = implode(',', $mediaIds);
+        }
+
+        $responseJson = Twitter::postTweet($tweetOpts);
+        $response = json_decode($responseJson);
+        $tweetId = $response->id;
+        $this->note->tweet_id = $tweetId;
+        $this->note->save();
+    }
+
+    /**
+     * Swap @names in a note.
+     *
+     * When a note is being saved and we are posting it to twitter, we want
+     * to swap our @local_name to Twitter’s @twitter_name so the user get’s
+     * mentioned on Twitter.
+     *
+     * @param  string $note
+     * @return string $noteSwappedNames
+     */
+    private function swapNames($note)
+    {
+        $regex = '/\[.*?\](*SKIP)(*F)|@(\w+)/'; //match @alice but not [@bob](...)
+        $noteSwappedNames = preg_replace_callback(
+            $regex,
+            function ($matches) {
+                try {
+                    $contact = Contact::where('nick', '=', mb_strtolower($matches[1]))->firstOrFail();
+                } catch (\Illuminate\Database\Eloquent\ModelNotFoundException $e) {
+                    return '@' . $matches[1];
+                }
+                $twitterHandle = $contact->twitter;
+
+                return '@' . $twitterHandle;
+            },
+            $note
+        );
+
+        return $noteSwappedNames;
+    }
+}
diff --git a/app/Listeners/.gitkeep b/app/Listeners/.gitkeep
new file mode 100644
index 00000000..8b137891
--- /dev/null
+++ b/app/Listeners/.gitkeep
@@ -0,0 +1 @@
+
diff --git a/app/Note.php b/app/Note.php
new file mode 100644
index 00000000..29213e14
--- /dev/null
+++ b/app/Note.php
@@ -0,0 +1,230 @@
+belongsToMany('App\Tag');
+    }
+
+    /**
+     * Define the relationship with webmentions.
+     *
+     * @var array
+     */
+    public function webmentions()
+    {
+        return $this->morphMany('App\WebMention', 'commentable');
+    }
+
+    /**
+     * Definte the relationship with places.
+     *
+     * @var array
+     */
+    public function place()
+    {
+        return $this->belongsTo('App\Place');
+    }
+
+    /**
+     * We shall set a blacklist of non-modifiable model attributes.
+     *
+     * @var array
+     */
+    protected $guarded = ['id'];
+
+    /**
+     * The attributes that should be mutated to dates.
+     *
+     * @var array
+     */
+    protected $dates = ['deleted_at'];
+
+    /**
+     * A mutator to ensure that in-reply-to is always non-empty or null.
+     *
+     * @param  string  value
+     * @return string
+     */
+    public function setInReplyToAttribute($value)
+    {
+        $this->attributes['in_reply_to'] = empty($value) ? null : $value;
+    }
+
+    /**
+     * Normalize the note to Unicode FORM C.
+     *
+     * @param  string  $value
+     * @return string
+     */
+    public function setNoteAttribute($value)
+    {
+        $this->attributes['note'] = normalizer_normalize($value, Normalizer::FORM_C);
+    }
+
+    /**
+     * Pre-process notes for web-view.
+     *
+     * @param  string
+     * @return string
+     */
+    public function getNoteAttribute($value)
+    {
+        $unicode = new UnicodeTools();
+        $codepoints = $unicode->convertUnicodeCodepoints($value);
+        $markdown = new CommonMarkConverter();
+        $html = $markdown->convertToHtml($codepoints);
+        $hcards = $this->makeHCards($html);
+        $hashtags = $this->autoLinkHashtag($hcards);
+
+        return $hashtags;
+    }
+
+    /**
+     * Generate the NewBase60 ID from primary ID.
+     *
+     * @return string
+     */
+    public function getNb60idAttribute()
+    {
+        $numbers = new Numbers();
+
+        return $numbers->numto60($this->id);
+    }
+
+    /**
+     * The Long URL for a note.
+     *
+     * @return string
+     */
+    public function getLongurlAttribute()
+    {
+        return config('app.url') . '/notes/' . $this->nb60id;
+    }
+
+    /**
+     * The Short URL for a note.
+     *
+     * @return string
+     */
+    public function getShorturlAttribute()
+    {
+        return config('app.shorturl') . '/notes/' . $this->nb60id;
+    }
+
+    /**
+     * Get the relavent client name assocaited with the client id.
+     *
+     * @return string|null
+     */
+    public function getClientNameAttribute()
+    {
+        if ($this->client_id == null) {
+            return;
+        }
+        $name = Client::where('client_url', $this->client_id)->value('client_name');
+        if ($name == null) {
+            $url = parse_url($this->client_id);
+            if (isset($url['path'])) {
+                return $url['host'] . $url['path'];
+            }
+
+            return $url['host'];
+        }
+
+        return $name;
+    }
+
+    /**
+     * Take note that this method does two things, given @username (NOT [@username](URL)!)
+     * we try to create a fancy hcard from our contact info. If this is not possible
+     * due to lack of contact info, we assume @username is a twitter handle and link it
+     * as such.
+     *
+     * @param  string  The note’s text
+     * @return string
+     */
+    private function makeHCards($text)
+    {
+        $regex = '/\[.*?\](*SKIP)(*F)|@(\w+)/'; //match @alice but not [@bob](...)
+        $hcards = preg_replace_callback(
+            $regex,
+            function ($matches) {
+                try {
+                    $contact = Contact::where('nick', '=', mb_strtolower($matches[1]))->firstOrFail();
+                } catch (ModelNotFoundException $e) {
+                    return '' . $matches[0] . '';
+                }
+                $path = parse_url($contact->homepage)['host'];
+                $contact->photo = (file_exists(public_path() . '/assets/profile-images/' . $path . '/image')) ?
+                    '/assets/profile-images/' . $path . '/image'
+                :
+                    '/assets/profile-images/default-image';
+
+                return trim(view('mini-hcard-template', ['contact' => $contact])->render());
+            },
+            $text
+        );
+
+        return $hcards;
+    }
+
+    /**
+     * Given a string and section, finds all hashtags matching
+     * `#[\-_a-zA-Z0-9]+` and wraps them in an `a` element with
+     * `rel=tag` set and a `href` of 'section/tagged/' + tagname without the #.
+     *
+     * @param  string  The note
+     * @return string
+     */
+    private function autoLinkHashtag($text)
+    {
+        // $replacements = ["#tag" => "]
+        $replacements = [];
+        $matches = [];
+
+        if (preg_match_all('/(?<=^|\s)\#([a-zA-Z0-9\-\_]+)/i', $text, $matches, PREG_PATTERN_ORDER)) {
+            // Look up #tags, get Full name and URL
+            foreach ($matches[0] as $name) {
+                $name = str_replace('#', '', $name);
+                $replacements[$name] =
+                  '';
+            }
+
+            // Replace #tags with valid microformat-enabled link
+            foreach ($replacements as $name => $replacement) {
+                $text = str_replace('#' . $name, $replacement, $text);
+            }
+        }
+
+        return $text;
+    }
+}
diff --git a/app/Place.php b/app/Place.php
new file mode 100644
index 00000000..8cadf966
--- /dev/null
+++ b/app/Place.php
@@ -0,0 +1,133 @@
+hasMany('App\Note');
+    }
+
+    /**
+     * Get all places within a specified distance.
+     *
+     * @param  float latitude
+     * @param  float longitude
+     * @param  int maximum distance
+     * @todo Check this shit.
+     */
+    public static function near(float $lat, float $lng, int $distance)
+    {
+        $point = $lng . ' ' . $lat;
+        $distace = $distance ?? 1000;
+        $places = DB::select(DB::raw("select
+            name,
+            slug,
+            ST_AsText(location) AS location,
+            ST_Distance(
+                ST_GeogFromText('SRID=4326;POINT($point)'),
+                location
+            ) AS distance
+        from places
+        where ST_DWithin(
+            ST_GeogFromText('SRID=4326;POINT($point)'),
+            location,
+            $distance
+        ) ORDER BY distance"));
+
+        return $places;
+    }
+
+    /**
+     * Convert location to text.
+     *
+     * @param  text $value
+     * @return text
+     */
+    public function getLocationAttribute($value)
+    {
+        $result = DB::select(DB::raw("SELECT ST_AsText('$value')"));
+
+        return $result[0]->st_astext;
+    }
+
+    /**
+     * Get the latitude from the `location` property.
+     *
+     * @return string latitude
+     */
+    public function getLatitudeAttribute()
+    {
+        preg_match('/\((.*)\)/', $this->location, $latlng);
+
+        return explode(' ', $latlng[1])[1];
+    }
+
+    /**
+     * Get the longitude from the `location` property.
+     *
+     * @return string longitude
+     */
+    public function getLongitudeAttribute()
+    {
+        preg_match('/\((.*)\)/', $this->location, $latlng);
+
+        return explode(' ', $latlng[1])[0];
+    }
+
+    /**
+     * The Long URL for a place.
+     *
+     * @return string
+     */
+    public function getLongurlAttribute()
+    {
+        return config('app.url') . '/places/' . $this->slug;
+    }
+
+    /**
+     * The Short URL for a place.
+     *
+     * @return string
+     */
+    public function getShorturlAttribute()
+    {
+        return config('app.shorturl') . '/places/' . $this->slug;
+    }
+}
diff --git a/app/Policies/.gitkeep b/app/Policies/.gitkeep
new file mode 100644
index 00000000..8b137891
--- /dev/null
+++ b/app/Policies/.gitkeep
@@ -0,0 +1 @@
+
diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php
new file mode 100644
index 00000000..76b7af57
--- /dev/null
+++ b/app/Providers/AppServiceProvider.php
@@ -0,0 +1,57 @@
+getSize() > 5000000) {
+                        return false;
+                    }
+                }
+            }
+
+            return true;
+        });
+
+        //Add tags for notes
+        Note::created(function ($note) {
+            $noteprep = new NotePrep();
+            $tagsToAdd = [];
+            $tags = $noteprep->getTags($note->note);
+            foreach ($tags as $text) {
+                $tag = Tag::firstOrCreate(['tag' => $text]);
+                $tagsToAdd[] = $tag->id;
+            }
+            if (count($tagsToAdd > 0)) {
+                $note->tags()->attach($tagsToAdd);
+            }
+        });
+    }
+
+    /**
+     * Register any application services.
+     *
+     * @return void
+     */
+    public function register()
+    {
+        //
+    }
+}
diff --git a/app/Providers/AuthServiceProvider.php b/app/Providers/AuthServiceProvider.php
new file mode 100644
index 00000000..57d88ea3
--- /dev/null
+++ b/app/Providers/AuthServiceProvider.php
@@ -0,0 +1,31 @@
+ 'App\Policies\ModelPolicy',
+    ];
+
+    /**
+     * Register any application authentication / authorization services.
+     *
+     * @param  \Illuminate\Contracts\Auth\Access\Gate  $gate
+     * @return void
+     */
+    public function boot(GateContract $gate)
+    {
+        $this->registerPolicies($gate);
+
+        //
+    }
+}
diff --git a/app/Providers/EventServiceProvider.php b/app/Providers/EventServiceProvider.php
new file mode 100644
index 00000000..58ce9624
--- /dev/null
+++ b/app/Providers/EventServiceProvider.php
@@ -0,0 +1,33 @@
+ [
+            'App\Listeners\EventListener',
+        ],
+    ];
+
+    /**
+     * Register any other events for your application.
+     *
+     * @param  \Illuminate\Contracts\Events\Dispatcher  $events
+     * @return void
+     */
+    public function boot(DispatcherContract $events)
+    {
+        parent::boot($events);
+
+        //
+    }
+}
diff --git a/app/Providers/RouteServiceProvider.php b/app/Providers/RouteServiceProvider.php
new file mode 100644
index 00000000..bde08819
--- /dev/null
+++ b/app/Providers/RouteServiceProvider.php
@@ -0,0 +1,61 @@
+mapWebRoutes($router);
+
+        //
+    }
+
+    /**
+     * Define the "web" routes for the application.
+     *
+     * These routes all receive session state, CSRF protection, etc.
+     *
+     * @param  \Illuminate\Routing\Router  $router
+     * @return void
+     */
+    protected function mapWebRoutes(Router $router)
+    {
+        $router->group([
+            'namespace' => $this->namespace, 'middleware' => 'web',
+        ], function ($router) {
+            require app_path('Http/routes.php');
+        });
+    }
+}
diff --git a/app/Services/IndieAuthService.php b/app/Services/IndieAuthService.php
new file mode 100644
index 00000000..cc7c775c
--- /dev/null
+++ b/app/Services/IndieAuthService.php
@@ -0,0 +1,113 @@
+discoverAuthorizationEndpoint($client->normalizeMeURL($domain));
+    }
+
+    /**
+     * Given an authorization endpoint, build the appropriate authorization URL.
+     *
+     * @param  string $authEndpoint
+     * @param  string $domain
+     * @param  \IndieAuth\Client $client
+     * @return string
+     */
+    public function buildAuthorizationURL($authEndpoint, $domain, $client)
+    {
+        $domain = $client->normalizeMeURL($domain);
+        $state = bin2hex(openssl_random_pseudo_bytes(16));
+        session(['state' => $state]);
+        $redirectURL = config('app.url') . '/indieauth';
+        $clientId = config('app.url') . '/notes/new';
+        $scope = 'post';
+        $authorizationURL = $client->buildAuthorizationURL(
+            $authEndpoint,
+            $domain,
+            $redirectURL,
+            $clientId,
+            $state,
+            $scope
+        );
+
+        return $authorizationURL;
+    }
+
+    /**
+     * Discover the token endpoint for a given domain.
+     *
+     * @param  string The domain
+     * @param  \IndieAuth\Client $client
+     * @return string|null
+     */
+    public function getTokenEndpoint($domain, $client)
+    {
+        return $client->discoverTokenEndpoint($domain);
+    }
+
+    /**
+     * Retrieve a token from the token endpoint.
+     *
+     * @param  array The relavent data
+     * @param  \IndieAuth\Client $client
+     * @return array
+     */
+    public function getAccessToken(array $data, $client)
+    {
+        return $client->getAccessToken(
+            $data['endpoint'],
+            $data['code'],
+            $data['me'],
+            $data['redirect_url'],
+            $data['client_id'],
+            $data['state']
+        );
+    }
+
+    /**
+     * Determine the Authorization endpoint, then verify the suplied code is
+     * valid.
+     *
+     * @param  array The data.
+     * @param  \IndieAuth\Client $client
+     * @return array|null
+     */
+    public function verifyIndieAuthCode(array $data, $client)
+    {
+        $authEndpoint = $client->discoverAuthorizationEndpoint($data['me']);
+        if ($authEndpoint) {
+            return $client->verifyIndieAuthCode(
+                $authEndpoint,
+                $data['code'],
+                $data['me'],
+                $data['redirect_url'],
+                $data['client_id'],
+                $data['state']
+            );
+        }
+    }
+
+    /**
+     * Determine the micropub endpoint.
+     *
+     * @param  string $domain
+     * @param  \IndieAuth\Client $client
+     * @return string The endpoint
+     */
+    public function discoverMicropubEndpoint($domain, $client)
+    {
+        return $client->discoverMicropubEndpoint($client->normalizeMeURL($domain));
+    }
+}
diff --git a/app/Services/NoteService.php b/app/Services/NoteService.php
new file mode 100644
index 00000000..61b970e0
--- /dev/null
+++ b/app/Services/NoteService.php
@@ -0,0 +1,67 @@
+ $request->input('content'),
+                'in_reply_to' => $request->input('in-reply-to'),
+                'client_id' => $clientId,
+            ]
+        );
+
+        $placeSlug = $request->input('location');
+        if ($placeSlug !== null && $placeSlug !== 'no-location') {
+            $place = Place::where('slug', '=', $placeSlug)->first();
+            $note->place()->associate($place);
+            $note->save();
+        }
+
+        //add images to media library
+        if ($request->hasFile('photo')) {
+            $files = $request->file('photo');
+            foreach ($files as $file) {
+                $note->addMedia($file)->toMediaLibraryOnDisk('images', 's3');
+            }
+        }
+
+        if ($request->input('webmentions')) {
+            $wmc = new WebMentionsController();
+            $wmc->send($note);
+        }
+
+        if (//micropub request, syndication sent as array
+            (is_array($request->input('mp-syndicate-to'))
+                &&
+            (in_array('twitter.com/jonnybarnes', $request->input('mp-syndicate-to')))
+            || //micropub request, syndication sent as string
+            ($request->input('mp-syndicate-to') == 'twitter.com/jonnybarnes')
+            || //local admin cp request
+            ($request->input('twitter') == true))
+        ) {
+            $this->dispatch(new SyndicateToTwitter($note));
+        }
+
+        return $note;
+    }
+}
diff --git a/app/Services/PlaceService.php b/app/Services/PlaceService.php
new file mode 100644
index 00000000..b4f737af
--- /dev/null
+++ b/app/Services/PlaceService.php
@@ -0,0 +1,39 @@
+input('geo') !== null) {
+            $parts = explode(':', $request->input('geo'));
+            $latlng = explode(',', $parts[1]);
+            $latitude = $latlng[0];
+            $longitude = $latlng[1];
+        }
+        if ($request->input('latitude') !== null) {
+            $latitude = $request->input('latitude');
+            $longitude = $request->input('longitude');
+        }
+        $place = new Place();
+        $place->name = $request->input('name');
+        $place->description = $request->input('description');
+        $place->location = new Point((float) $latitude, (float) $longitude);
+        $place->save();
+
+        return $place;
+    }
+}
diff --git a/app/Services/TokenService.php b/app/Services/TokenService.php
new file mode 100644
index 00000000..4652d30b
--- /dev/null
+++ b/app/Services/TokenService.php
@@ -0,0 +1,54 @@
+set('me', $data['me'])
+            ->set('client_id', $data['client_id'])
+            ->set('scope', $data['scope'])
+            ->set('date_issued', time())
+            ->set('nonce', bin2hex(random_bytes(8)))
+            ->sign($signer, env('APP_KEY'))
+            ->getToken();
+
+        return $token;
+    }
+
+    /**
+     * Check the token signature is valid.
+     *
+     * @param  string The token
+     * @return mixed
+     */
+    public function validateToken($token)
+    {
+        $signer = new Sha256();
+        try {
+            $token = (new Parser())->parse((string) $token);
+        } catch (InvalidArgumentException $e) {
+            return;
+        } catch (RuntimeException $e) {
+            return;
+        }
+        if ($token->verify($signer, env('APP_KEY'))) {
+            //signuture valid
+            return $token;
+        }
+    }
+}
diff --git a/app/Tag.php b/app/Tag.php
new file mode 100644
index 00000000..fb55663a
--- /dev/null
+++ b/app/Tag.php
@@ -0,0 +1,39 @@
+belongsToMany('App\Note');
+    }
+
+    /**
+     * The attributes excluded from the model's JSON form.
+     *
+     * @var array
+     */
+    protected $hidden = ['deleted'];
+
+    /**
+     * We shall set a blacklist of non-modifiable model attributes.
+     *
+     * @var array
+     */
+    protected $guarded = ['id'];
+}
diff --git a/app/User.php b/app/User.php
new file mode 100644
index 00000000..75741ae4
--- /dev/null
+++ b/app/User.php
@@ -0,0 +1,26 @@
+morphTo();
+    }
+
+    /**
+     * We shall set a blacklist of non-modifiable model attributes.
+     *
+     * @var array
+     */
+    protected $guarded = ['id'];
+}
diff --git a/artisan b/artisan
new file mode 100755
index 00000000..df630d0d
--- /dev/null
+++ b/artisan
@@ -0,0 +1,51 @@
+#!/usr/bin/env php
+make(Illuminate\Contracts\Console\Kernel::class);
+
+$status = $kernel->handle(
+    $input = new Symfony\Component\Console\Input\ArgvInput,
+    new Symfony\Component\Console\Output\ConsoleOutput
+);
+
+/*
+|--------------------------------------------------------------------------
+| Shutdown The Application
+|--------------------------------------------------------------------------
+|
+| Once Artisan has finished running. We will fire off the shutdown events
+| so that any final work may be done by the application before we shut
+| down the process. This is the last thing to happen to the request.
+|
+*/
+
+$kernel->terminate($input, $status);
+
+exit($status);
diff --git a/bootstrap/app.php b/bootstrap/app.php
new file mode 100644
index 00000000..f2801adf
--- /dev/null
+++ b/bootstrap/app.php
@@ -0,0 +1,55 @@
+singleton(
+    Illuminate\Contracts\Http\Kernel::class,
+    App\Http\Kernel::class
+);
+
+$app->singleton(
+    Illuminate\Contracts\Console\Kernel::class,
+    App\Console\Kernel::class
+);
+
+$app->singleton(
+    Illuminate\Contracts\Debug\ExceptionHandler::class,
+    App\Exceptions\Handler::class
+);
+
+/*
+|--------------------------------------------------------------------------
+| Return The Application
+|--------------------------------------------------------------------------
+|
+| This script returns the application instance. The instance is given to
+| the calling script so we can separate the building of the instances
+| from the actual running of the application and sending responses.
+|
+*/
+
+return $app;
diff --git a/bootstrap/autoload.php b/bootstrap/autoload.php
new file mode 100644
index 00000000..38301379
--- /dev/null
+++ b/bootstrap/autoload.php
@@ -0,0 +1,34 @@
+"
+  ],
+  "license": "CC0-1.0",
+  "homepage": "https://github.com/jonnybarnes/jbl5",
+  "moduleType": [],
+  "ignore": [
+    "**/.*",
+    "node_modules",
+    "bower_components",
+    "test",
+    "tests"
+  ],
+  "dependencies": {
+    "fetch": "~0.11.0",
+    "alertify.js": "alertifyjs#~1.0.5",
+    "store2": "~2.3.2",
+    "Autolinker.js": "~0.24.0",
+    "marked": "~0.3.5",
+    "sanitize-css": "^3.2.0"
+  }
+}
diff --git a/changelog.md b/changelog.md
new file mode 100644
index 00000000..9ff53f79
--- /dev/null
+++ b/changelog.md
@@ -0,0 +1,4 @@
+# Changelog
+
+## Version 0.0.1 (2016-05-25)
+Initial release
diff --git a/composer.json b/composer.json
new file mode 100644
index 00000000..f9969344
--- /dev/null
+++ b/composer.json
@@ -0,0 +1,74 @@
+{
+    "name": "jonnybarnes/jonnybarnes.uk",
+    "description": "The code for jonnybanres.uk, based on Laravel 5.2",
+    "keywords": ["framework", "laravel", "indieweb"],
+    "license": "CC0-1.0",
+    "type": "project",
+    "require": {
+        "ext-intl": "*",
+        "php": ">=7.0.0",
+        "laravel/framework": "5.2.*",
+        "jonnybarnes/unicode-tools": "dev-master",
+        "jonnybarnes/indieweb": "dev-master",
+        "jonnybarnes/webmentions-parser": "dev-master",
+        "guzzlehttp/guzzle": "~6.0",
+        "predis/predis": "~1.0",
+        "thujohn/twitter": "~2.0",
+        "mf2/mf2": "~0.3",
+        "martinbean/laravel-sluggable-trait": "0.2.*",
+        "indieauth/client": "~0.1",
+        "ezyang/htmlpurifier": "~4.6",
+        "league/commonmark": "^0.13.0",
+        "spatie/laravel-medialibrary": "^3.5",
+        "league/flysystem-aws-s3-v3": "^1.0",
+        "phaza/laravel-postgis": "dev-master",
+        "lcobucci/jwt": "^3.1"
+    },
+    "require-dev": {
+        "fzaninotto/faker": "~1.4",
+        "mockery/mockery": "0.9.*",
+        "phpunit/phpunit": "~4.0",
+        "symfony/css-selector": "2.8.*|3.0.*",
+        "symfony/dom-crawler": "2.8.*|3.0.*",
+        "barryvdh/laravel-debugbar": "~2.0",
+        "filp/whoops": "~2.0"
+    },
+    "repositories": [
+        {
+            "type": "vcs",
+            "url": "https://github.com/njbarrett/laravel-postgis"
+        }
+    ],
+    "autoload": {
+        "classmap": [
+            "database"
+        ],
+        "psr-4": {
+            "App\\": "app/"
+        }
+    },
+    "autoload-dev": {
+        "classmap": [
+            "tests/TestCase.php"
+        ]
+    },
+    "scripts": {
+        "post-root-package-install": [
+            "php -r \"copy('.env.example', '.env');\""
+        ],
+        "post-create-project-cmd": [
+            "php artisan key:generate"
+        ],
+        "post-install-cmd": [
+            "Illuminate\\Foundation\\ComposerScripts::postInstall",
+            "php artisan optimize"
+        ],
+        "post-update-cmd": [
+            "Illuminate\\Foundation\\ComposerScripts::postUpdate",
+            "php artisan optimize"
+        ]
+    },
+    "config": {
+        "preferred-install": "dist"
+    }
+}
diff --git a/composer.lock b/composer.lock
new file mode 100644
index 00000000..906cbbba
--- /dev/null
+++ b/composer.lock
@@ -0,0 +1,5039 @@
+{
+    "_readme": [
+        "This file locks the dependencies of your project to a known state",
+        "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
+        "This file is @generated automatically"
+    ],
+    "hash": "daf7804a781d9e4f7de63ae2da8dee9e",
+    "content-hash": "59a5ff5c293a0de8dbc63e7b8f0cf055",
+    "packages": [
+        {
+            "name": "anahkiasen/underscore-php",
+            "version": "2.0.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/Anahkiasen/underscore-php.git",
+                "reference": "48f97b295c82d99c1fe10d8b0684c43f051b5580"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/Anahkiasen/underscore-php/zipball/48f97b295c82d99c1fe10d8b0684c43f051b5580",
+                "reference": "48f97b295c82d99c1fe10d8b0684c43f051b5580",
+                "shasum": ""
+            },
+            "require": {
+                "doctrine/inflector": "^1.0",
+                "patchwork/utf8": "^1.2",
+                "php": ">=5.4.0"
+            },
+            "require-dev": {
+                "fabpot/php-cs-fixer": "2.0.*@dev",
+                "phpunit/phpunit": "^4.6"
+            },
+            "type": "library",
+            "autoload": {
+                "psr-4": {
+                    "Underscore\\": [
+                        "src",
+                        "tests"
+                    ]
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Maxime Fabre",
+                    "email": "ehtnam6@gmail.com"
+                }
+            ],
+            "description": "A redacted port of Underscore.js for PHP",
+            "keywords": [
+                "internals",
+                "laravel",
+                "toolkit"
+            ],
+            "time": "2015-05-16 19:24:58"
+        },
+        {
+            "name": "aws/aws-sdk-php",
+            "version": "3.18.12",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/aws/aws-sdk-php.git",
+                "reference": "12386ad98e3a76df29cee9475264b7364a50542f"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/12386ad98e3a76df29cee9475264b7364a50542f",
+                "reference": "12386ad98e3a76df29cee9475264b7364a50542f",
+                "shasum": ""
+            },
+            "require": {
+                "guzzlehttp/guzzle": "~5.3|~6.0.1|~6.1",
+                "guzzlehttp/promises": "~1.0",
+                "guzzlehttp/psr7": "~1.0",
+                "mtdowling/jmespath.php": "~2.2",
+                "php": ">=5.5"
+            },
+            "require-dev": {
+                "andrewsville/php-token-reflection": "^1.4",
+                "aws/aws-php-sns-message-validator": "~1.0",
+                "behat/behat": "~3.0",
+                "doctrine/cache": "~1.4",
+                "ext-dom": "*",
+                "ext-json": "*",
+                "ext-openssl": "*",
+                "ext-pcre": "*",
+                "ext-simplexml": "*",
+                "ext-spl": "*",
+                "nette/neon": "^2.3",
+                "phpunit/phpunit": "~4.0|~5.0",
+                "psr/cache": "^1.0"
+            },
+            "suggest": {
+                "aws/aws-php-sns-message-validator": "To validate incoming SNS notifications",
+                "doctrine/cache": "To use the DoctrineCacheAdapter",
+                "ext-curl": "To send requests using cURL",
+                "ext-openssl": "Allows working with CloudFront private distributions and verifying received SNS messages"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "3.0-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Aws\\": "src/"
+                },
+                "files": [
+                    "src/functions.php"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "Apache-2.0"
+            ],
+            "authors": [
+                {
+                    "name": "Amazon Web Services",
+                    "homepage": "http://aws.amazon.com"
+                }
+            ],
+            "description": "AWS SDK for PHP - Use Amazon Web Services in your PHP project",
+            "homepage": "http://aws.amazon.com/sdkforphp",
+            "keywords": [
+                "amazon",
+                "aws",
+                "cloud",
+                "dynamodb",
+                "ec2",
+                "glacier",
+                "s3",
+                "sdk"
+            ],
+            "time": "2016-05-20 05:19:30"
+        },
+        {
+            "name": "barnabywalters/mf-cleaner",
+            "version": "v0.1.4",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/barnabywalters/php-mf-cleaner.git",
+                "reference": "ef6a16628db6e8aee2b4f8bb8093d18c24b74cd4"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/barnabywalters/php-mf-cleaner/zipball/ef6a16628db6e8aee2b4f8bb8093d18c24b74cd4",
+                "reference": "ef6a16628db6e8aee2b4f8bb8093d18c24b74cd4",
+                "shasum": ""
+            },
+            "require-dev": {
+                "php": ">=5.3",
+                "phpunit/phpunit": "*"
+            },
+            "suggest": {
+                "mf2/mf2": "To parse microformats2 structures from (X)HTML"
+            },
+            "type": "library",
+            "autoload": {
+                "files": [
+                    "src/BarnabyWalters/Mf2/Functions.php"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Barnaby Walters",
+                    "email": "barnaby@waterpigs.co.uk"
+                }
+            ],
+            "description": "Cleans up microformats2 array structures",
+            "time": "2014-10-06 23:11:15"
+        },
+        {
+            "name": "bosnadev/database",
+            "version": "0.16",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/Bosnadev/Database.git",
+                "reference": "c2748d118415d30ce69b792448689285d01ffdb9"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/Bosnadev/Database/zipball/c2748d118415d30ce69b792448689285d01ffdb9",
+                "reference": "c2748d118415d30ce69b792448689285d01ffdb9",
+                "shasum": ""
+            },
+            "require": {
+                "illuminate/database": "~5.0",
+                "php": ">=5.4",
+                "ramsey/uuid": "~3.0"
+            },
+            "require-dev": {
+                "doctrine/dbal": "~2.5",
+                "mockery/mockery": "0.9.*",
+                "php": ">=5.4",
+                "phpunit/phpunit": "~4.5"
+            },
+            "type": "library",
+            "autoload": {
+                "psr-4": {
+                    "Bosnadev\\Database\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "authors": [
+                {
+                    "name": "Mirza Pasic",
+                    "email": "mirza.pasic@edu.fit.ba"
+                },
+                {
+                    "name": "Peter Haza",
+                    "email": "peter.haza@gmail.com"
+                }
+            ],
+            "description": "Eloquent Extended, added some PostgreSql features",
+            "keywords": [
+                "database",
+                "eloquent",
+                "laravel",
+                "mysql",
+                "postgresql"
+            ],
+            "time": "2016-04-27 15:18:36"
+        },
+        {
+            "name": "classpreloader/classpreloader",
+            "version": "3.0.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/ClassPreloader/ClassPreloader.git",
+                "reference": "9b10b913c2bdf90c3d2e0d726b454fb7f77c552a"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/ClassPreloader/ClassPreloader/zipball/9b10b913c2bdf90c3d2e0d726b454fb7f77c552a",
+                "reference": "9b10b913c2bdf90c3d2e0d726b454fb7f77c552a",
+                "shasum": ""
+            },
+            "require": {
+                "nikic/php-parser": "^1.0|^2.0",
+                "php": ">=5.5.9"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "^4.8|^5.0"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "3.0-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "ClassPreloader\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Michael Dowling",
+                    "email": "mtdowling@gmail.com"
+                },
+                {
+                    "name": "Graham Campbell",
+                    "email": "graham@alt-three.com"
+                }
+            ],
+            "description": "Helps class loading performance by generating a single PHP file containing all of the autoloaded files for a specific use case",
+            "keywords": [
+                "autoload",
+                "class",
+                "preload"
+            ],
+            "time": "2015-11-09 22:51:51"
+        },
+        {
+            "name": "dnoegel/php-xdg-base-dir",
+            "version": "0.1",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/dnoegel/php-xdg-base-dir.git",
+                "reference": "265b8593498b997dc2d31e75b89f053b5cc9621a"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/dnoegel/php-xdg-base-dir/zipball/265b8593498b997dc2d31e75b89f053b5cc9621a",
+                "reference": "265b8593498b997dc2d31e75b89f053b5cc9621a",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.3.2"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "@stable"
+            },
+            "type": "project",
+            "autoload": {
+                "psr-4": {
+                    "XdgBaseDir\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "description": "implementation of xdg base directory specification for php",
+            "time": "2014-10-24 07:27:01"
+        },
+        {
+            "name": "doctrine/inflector",
+            "version": "v1.1.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/doctrine/inflector.git",
+                "reference": "90b2128806bfde671b6952ab8bea493942c1fdae"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/doctrine/inflector/zipball/90b2128806bfde671b6952ab8bea493942c1fdae",
+                "reference": "90b2128806bfde671b6952ab8bea493942c1fdae",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.3.2"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "4.*"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.1.x-dev"
+                }
+            },
+            "autoload": {
+                "psr-0": {
+                    "Doctrine\\Common\\Inflector\\": "lib/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Roman Borschel",
+                    "email": "roman@code-factory.org"
+                },
+                {
+                    "name": "Benjamin Eberlei",
+                    "email": "kontakt@beberlei.de"
+                },
+                {
+                    "name": "Guilherme Blanco",
+                    "email": "guilhermeblanco@gmail.com"
+                },
+                {
+                    "name": "Jonathan Wage",
+                    "email": "jonwage@gmail.com"
+                },
+                {
+                    "name": "Johannes Schmitt",
+                    "email": "schmittjoh@gmail.com"
+                }
+            ],
+            "description": "Common String Manipulations with regard to casing and singular/plural rules.",
+            "homepage": "http://www.doctrine-project.org",
+            "keywords": [
+                "inflection",
+                "pluralize",
+                "singularize",
+                "string"
+            ],
+            "time": "2015-11-06 14:35:42"
+        },
+        {
+            "name": "ezyang/htmlpurifier",
+            "version": "v4.7.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/ezyang/htmlpurifier.git",
+                "reference": "ae1828d955112356f7677c465f94f7deb7d27a40"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/ezyang/htmlpurifier/zipball/ae1828d955112356f7677c465f94f7deb7d27a40",
+                "reference": "ae1828d955112356f7677c465f94f7deb7d27a40",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.2"
+            },
+            "type": "library",
+            "autoload": {
+                "psr-0": {
+                    "HTMLPurifier": "library/"
+                },
+                "files": [
+                    "library/HTMLPurifier.composer.php"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "LGPL"
+            ],
+            "authors": [
+                {
+                    "name": "Edward Z. Yang",
+                    "email": "admin@htmlpurifier.org",
+                    "homepage": "http://ezyang.com"
+                }
+            ],
+            "description": "Standards compliant HTML filter written in PHP",
+            "homepage": "http://htmlpurifier.org/",
+            "keywords": [
+                "html"
+            ],
+            "time": "2015-08-05 01:03:42"
+        },
+        {
+            "name": "geo-io/interface",
+            "version": "v1.0.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/geo-io/interface.git",
+                "reference": "cdbb55801e3f8d5485227c2031cc7a3c16ccd06a"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/geo-io/interface/zipball/cdbb55801e3f8d5485227c2031cc7a3c16ccd06a",
+                "reference": "cdbb55801e3f8d5485227c2031cc7a3c16ccd06a",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.3.3"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.0-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "GeoIO\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Jan Sorgalla",
+                    "email": "jsorgalla@gmail.com"
+                }
+            ],
+            "description": "Geo I/O base interfaces.",
+            "keywords": [
+                "geo",
+                "geometry",
+                "io"
+            ],
+            "time": "2015-04-17 18:52:52"
+        },
+        {
+            "name": "geo-io/wkb-parser",
+            "version": "v1.0.1",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/geo-io/wkb-parser.git",
+                "reference": "cceee8f4e8b2058f3f1a0372c930140f23fe1ee1"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/geo-io/wkb-parser/zipball/cceee8f4e8b2058f3f1a0372c930140f23fe1ee1",
+                "reference": "cceee8f4e8b2058f3f1a0372c930140f23fe1ee1",
+                "shasum": ""
+            },
+            "require": {
+                "geo-io/interface": "~1.0",
+                "php": ">=5.3.3"
+            },
+            "require-dev": {
+                "mockery/mockery": "~0.9.0"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.0-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "GeoIO\\WKB\\Parser\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Jan Sorgalla",
+                    "email": "jsorgalla@gmail.com"
+                }
+            ],
+            "description": "Well-known binary (WKB) Parser.",
+            "keywords": [
+                "geo",
+                "geometry",
+                "io",
+                "parser",
+                "wkb"
+            ],
+            "time": "2015-06-30 04:19:13"
+        },
+        {
+            "name": "guzzlehttp/guzzle",
+            "version": "6.2.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/guzzle/guzzle.git",
+                "reference": "d094e337976dff9d8e2424e8485872194e768662"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/guzzle/guzzle/zipball/d094e337976dff9d8e2424e8485872194e768662",
+                "reference": "d094e337976dff9d8e2424e8485872194e768662",
+                "shasum": ""
+            },
+            "require": {
+                "guzzlehttp/promises": "~1.0",
+                "guzzlehttp/psr7": "~1.1",
+                "php": ">=5.5.0"
+            },
+            "require-dev": {
+                "ext-curl": "*",
+                "phpunit/phpunit": "~4.0",
+                "psr/log": "~1.0"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "6.2-dev"
+                }
+            },
+            "autoload": {
+                "files": [
+                    "src/functions_include.php"
+                ],
+                "psr-4": {
+                    "GuzzleHttp\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Michael Dowling",
+                    "email": "mtdowling@gmail.com",
+                    "homepage": "https://github.com/mtdowling"
+                }
+            ],
+            "description": "Guzzle is a PHP HTTP client library",
+            "homepage": "http://guzzlephp.org/",
+            "keywords": [
+                "client",
+                "curl",
+                "framework",
+                "http",
+                "http client",
+                "rest",
+                "web service"
+            ],
+            "time": "2016-03-21 20:02:09"
+        },
+        {
+            "name": "guzzlehttp/promises",
+            "version": "1.2.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/guzzle/promises.git",
+                "reference": "c10d860e2a9595f8883527fa0021c7da9e65f579"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/guzzle/promises/zipball/c10d860e2a9595f8883527fa0021c7da9e65f579",
+                "reference": "c10d860e2a9595f8883527fa0021c7da9e65f579",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.5.0"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "~4.0"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.0-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "GuzzleHttp\\Promise\\": "src/"
+                },
+                "files": [
+                    "src/functions_include.php"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Michael Dowling",
+                    "email": "mtdowling@gmail.com",
+                    "homepage": "https://github.com/mtdowling"
+                }
+            ],
+            "description": "Guzzle promises library",
+            "keywords": [
+                "promise"
+            ],
+            "time": "2016-05-18 16:56:05"
+        },
+        {
+            "name": "guzzlehttp/psr7",
+            "version": "1.3.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/guzzle/psr7.git",
+                "reference": "31382fef2889136415751badebbd1cb022a4ed72"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/guzzle/psr7/zipball/31382fef2889136415751badebbd1cb022a4ed72",
+                "reference": "31382fef2889136415751badebbd1cb022a4ed72",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.4.0",
+                "psr/http-message": "~1.0"
+            },
+            "provide": {
+                "psr/http-message-implementation": "1.0"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "~4.0"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.0-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "GuzzleHttp\\Psr7\\": "src/"
+                },
+                "files": [
+                    "src/functions_include.php"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Michael Dowling",
+                    "email": "mtdowling@gmail.com",
+                    "homepage": "https://github.com/mtdowling"
+                }
+            ],
+            "description": "PSR-7 message implementation",
+            "keywords": [
+                "http",
+                "message",
+                "stream",
+                "uri"
+            ],
+            "time": "2016-04-13 19:56:01"
+        },
+        {
+            "name": "indieauth/client",
+            "version": "0.1.14",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/indieweb/indieauth-client-php.git",
+                "reference": "504ba095ee10ffaabc570682f3a93b462ba21c77"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/indieweb/indieauth-client-php/zipball/504ba095ee10ffaabc570682f3a93b462ba21c77",
+                "reference": "504ba095ee10ffaabc570682f3a93b462ba21c77",
+                "shasum": ""
+            },
+            "require": {
+                "barnabywalters/mf-cleaner": "0.*",
+                "indieweb/link-rel-parser": "0.1.1",
+                "mf2/mf2": "~0.3",
+                "php": ">5.3.0"
+            },
+            "type": "library",
+            "autoload": {
+                "psr-0": {
+                    "IndieAuth": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "Apache 2.0"
+            ],
+            "authors": [
+                {
+                    "name": "Aaron Parecki",
+                    "homepage": "http://aaronparecki.com"
+                }
+            ],
+            "description": "IndieAuth Client Library",
+            "time": "2016-04-04 14:57:04"
+        },
+        {
+            "name": "indieweb/link-rel-parser",
+            "version": "0.1.1",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/indieweb/link-rel-parser-php.git",
+                "reference": "9e0e635fd301a8b1da7bc181f651f029c531dbb6"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/indieweb/link-rel-parser-php/zipball/9e0e635fd301a8b1da7bc181f651f029c531dbb6",
+                "reference": "9e0e635fd301a8b1da7bc181f651f029c531dbb6",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.3.0"
+            },
+            "type": "library",
+            "autoload": {
+                "files": [
+                    "src/IndieWeb/link_rel_parser.php"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "Apache-2.0"
+            ],
+            "authors": [
+                {
+                    "name": "Aaron Parecki",
+                    "homepage": "http://aaronparecki.com"
+                },
+                {
+                    "name": "Tantek Çelik",
+                    "homepage": "http://tantek.com"
+                }
+            ],
+            "description": "Parse rel values from HTTP headers",
+            "homepage": "https://github.com/indieweb/link-rel-parser-php",
+            "keywords": [
+                "http",
+                "indieweb",
+                "microformats2"
+            ],
+            "time": "2013-12-23 00:14:58"
+        },
+        {
+            "name": "intervention/image",
+            "version": "2.3.7",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/Intervention/image.git",
+                "reference": "22088b04728a039bd1fc32f7e79a89a118b78698"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/Intervention/image/zipball/22088b04728a039bd1fc32f7e79a89a118b78698",
+                "reference": "22088b04728a039bd1fc32f7e79a89a118b78698",
+                "shasum": ""
+            },
+            "require": {
+                "ext-fileinfo": "*",
+                "guzzlehttp/psr7": "~1.1",
+                "php": ">=5.4.0"
+            },
+            "require-dev": {
+                "mockery/mockery": "~0.9.2",
+                "phpunit/phpunit": "3.*"
+            },
+            "suggest": {
+                "ext-gd": "to use GD library based image processing.",
+                "ext-imagick": "to use Imagick based image processing.",
+                "intervention/imagecache": "Caching extension for the Intervention Image library"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "2.3-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Intervention\\Image\\": "src/Intervention/Image"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Oliver Vogel",
+                    "email": "oliver@olivervogel.net",
+                    "homepage": "http://olivervogel.net/"
+                }
+            ],
+            "description": "Image handling and manipulation library with support for Laravel integration",
+            "homepage": "http://image.intervention.io/",
+            "keywords": [
+                "gd",
+                "image",
+                "imagick",
+                "laravel",
+                "thumbnail",
+                "watermark"
+            ],
+            "time": "2016-04-26 14:08:40"
+        },
+        {
+            "name": "jakub-onderka/php-console-color",
+            "version": "0.1",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/JakubOnderka/PHP-Console-Color.git",
+                "reference": "e0b393dacf7703fc36a4efc3df1435485197e6c1"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/JakubOnderka/PHP-Console-Color/zipball/e0b393dacf7703fc36a4efc3df1435485197e6c1",
+                "reference": "e0b393dacf7703fc36a4efc3df1435485197e6c1",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.3.2"
+            },
+            "require-dev": {
+                "jakub-onderka/php-code-style": "1.0",
+                "jakub-onderka/php-parallel-lint": "0.*",
+                "jakub-onderka/php-var-dump-check": "0.*",
+                "phpunit/phpunit": "3.7.*",
+                "squizlabs/php_codesniffer": "1.*"
+            },
+            "type": "library",
+            "autoload": {
+                "psr-0": {
+                    "JakubOnderka\\PhpConsoleColor": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-2-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Jakub Onderka",
+                    "email": "jakub.onderka@gmail.com",
+                    "homepage": "http://www.acci.cz"
+                }
+            ],
+            "time": "2014-04-08 15:00:19"
+        },
+        {
+            "name": "jakub-onderka/php-console-highlighter",
+            "version": "v0.3.2",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/JakubOnderka/PHP-Console-Highlighter.git",
+                "reference": "7daa75df45242c8d5b75a22c00a201e7954e4fb5"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/JakubOnderka/PHP-Console-Highlighter/zipball/7daa75df45242c8d5b75a22c00a201e7954e4fb5",
+                "reference": "7daa75df45242c8d5b75a22c00a201e7954e4fb5",
+                "shasum": ""
+            },
+            "require": {
+                "jakub-onderka/php-console-color": "~0.1",
+                "php": ">=5.3.0"
+            },
+            "require-dev": {
+                "jakub-onderka/php-code-style": "~1.0",
+                "jakub-onderka/php-parallel-lint": "~0.5",
+                "jakub-onderka/php-var-dump-check": "~0.1",
+                "phpunit/phpunit": "~4.0",
+                "squizlabs/php_codesniffer": "~1.5"
+            },
+            "type": "library",
+            "autoload": {
+                "psr-0": {
+                    "JakubOnderka\\PhpConsoleHighlighter": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Jakub Onderka",
+                    "email": "acci@acci.cz",
+                    "homepage": "http://www.acci.cz/"
+                }
+            ],
+            "time": "2015-04-20 18:58:01"
+        },
+        {
+            "name": "jeremeamia/SuperClosure",
+            "version": "2.2.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/jeremeamia/super_closure.git",
+                "reference": "29a88be2a4846d27c1613aed0c9071dfad7b5938"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/jeremeamia/super_closure/zipball/29a88be2a4846d27c1613aed0c9071dfad7b5938",
+                "reference": "29a88be2a4846d27c1613aed0c9071dfad7b5938",
+                "shasum": ""
+            },
+            "require": {
+                "nikic/php-parser": "^1.2|^2.0",
+                "php": ">=5.4",
+                "symfony/polyfill-php56": "^1.0"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "^4.0|^5.0"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "2.2-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "SuperClosure\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Jeremy Lindblom",
+                    "email": "jeremeamia@gmail.com",
+                    "homepage": "https://github.com/jeremeamia",
+                    "role": "Developer"
+                }
+            ],
+            "description": "Serialize Closure objects, including their context and binding",
+            "homepage": "https://github.com/jeremeamia/super_closure",
+            "keywords": [
+                "closure",
+                "function",
+                "lambda",
+                "parser",
+                "serializable",
+                "serialize",
+                "tokenizer"
+            ],
+            "time": "2015-12-05 17:17:57"
+        },
+        {
+            "name": "jmikola/geojson",
+            "version": "1.0.2",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/jmikola/geojson.git",
+                "reference": "6ec3016cc0215667b7775f6ead7bd0337ad66eee"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/jmikola/geojson/zipball/6ec3016cc0215667b7775f6ead7bd0337ad66eee",
+                "reference": "6ec3016cc0215667b7775f6ead7bd0337ad66eee",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.3.3"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "~3.7"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.0-dev"
+                }
+            },
+            "autoload": {
+                "psr-0": {
+                    "GeoJson\\": "src/"
+                },
+                "classmap": [
+                    "stubs/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Jeremy Mikola",
+                    "email": "jmikola@gmail.com"
+                }
+            ],
+            "description": "GeoJSON implementation for PHP",
+            "homepage": "https://github.com/jmikola/geojson",
+            "keywords": [
+                "geo",
+                "geojson",
+                "geospatial"
+            ],
+            "time": "2015-09-27 15:35:21"
+        },
+        {
+            "name": "jonnybarnes/indieweb",
+            "version": "dev-master",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/jonnybarnes/indieweb.git",
+                "reference": "a1de61e99dddd4a5800565bff3802a892a693b35"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/jonnybarnes/indieweb/zipball/a1de61e99dddd4a5800565bff3802a892a693b35",
+                "reference": "a1de61e99dddd4a5800565bff3802a892a693b35",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.6"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "~5.0"
+            },
+            "type": "library",
+            "autoload": {
+                "psr-4": {
+                    "Jonnybarnes\\IndieWeb\\": "src"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "CC0-1.0"
+            ],
+            "authors": [
+                {
+                    "name": "Jonny Barnes",
+                    "email": "jonny@jonnybarnes.uk"
+                }
+            ],
+            "description": "IndieWeb helper functions",
+            "homepage": "https://github.com/jonnybarnes/indieweb",
+            "keywords": [
+                "indieweb",
+                "posse"
+            ],
+            "time": "2016-01-14 15:10:20"
+        },
+        {
+            "name": "jonnybarnes/unicode-tools",
+            "version": "dev-master",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/jonnybarnes/unicode-tools.git",
+                "reference": "0f469c30cb9a40a1cb578f893b3af1abc1a6ff53"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/jonnybarnes/unicode-tools/zipball/0f469c30cb9a40a1cb578f893b3af1abc1a6ff53",
+                "reference": "0f469c30cb9a40a1cb578f893b3af1abc1a6ff53",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.3.0"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "3.7.*"
+            },
+            "type": "library",
+            "autoload": {
+                "psr-0": {
+                    "Jonnybarnes\\UnicodeTools": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Jonny Barnes",
+                    "email": "jonny@jonnybarnes.net"
+                }
+            ],
+            "description": "Turns Unicode codepoints into raw utf-8 multibyte characters",
+            "homepage": "https://github.com/jonnybarnes/unicode-tools",
+            "keywords": [
+                "unicode",
+                "utf-8"
+            ],
+            "time": "2013-07-18 15:32:42"
+        },
+        {
+            "name": "jonnybarnes/webmentions-parser",
+            "version": "dev-master",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/jonnybarnes/webmentions-parser.git",
+                "reference": "00ccf313a8c19bf795fc16ec71f2408ea23dd8d6"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/jonnybarnes/webmentions-parser/zipball/00ccf313a8c19bf795fc16ec71f2408ea23dd8d6",
+                "reference": "00ccf313a8c19bf795fc16ec71f2408ea23dd8d6",
+                "shasum": ""
+            },
+            "require": {
+                "guzzlehttp/guzzle": "~6.0",
+                "mf2/mf2": "~0.3",
+                "php": ">=5.6"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "~5.0"
+            },
+            "type": "library",
+            "autoload": {
+                "psr-4": {
+                    "Jonnybarnes\\WebmentionsParser\\": "src"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "authors": [
+                {
+                    "name": "Jonny Barnes",
+                    "homepage": "https://jonnybarnes.uk"
+                }
+            ],
+            "description": "A PHP library to parse webmentions from HTML",
+            "keywords": [
+                "html",
+                "indieweb",
+                "microformats",
+                "webmentions"
+            ],
+            "time": "2016-04-03 19:57:20"
+        },
+        {
+            "name": "laravel/framework",
+            "version": "v5.2.32",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/laravel/framework.git",
+                "reference": "f688217113f70b01d0e127da9035195415812bef"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/laravel/framework/zipball/f688217113f70b01d0e127da9035195415812bef",
+                "reference": "f688217113f70b01d0e127da9035195415812bef",
+                "shasum": ""
+            },
+            "require": {
+                "classpreloader/classpreloader": "~3.0",
+                "doctrine/inflector": "~1.0",
+                "ext-mbstring": "*",
+                "ext-openssl": "*",
+                "jeremeamia/superclosure": "~2.2",
+                "league/flysystem": "~1.0",
+                "monolog/monolog": "~1.11",
+                "mtdowling/cron-expression": "~1.0",
+                "nesbot/carbon": "~1.20",
+                "paragonie/random_compat": "~1.4",
+                "php": ">=5.5.9",
+                "psy/psysh": "0.7.*",
+                "swiftmailer/swiftmailer": "~5.1",
+                "symfony/console": "2.8.*|3.0.*",
+                "symfony/debug": "2.8.*|3.0.*",
+                "symfony/finder": "2.8.*|3.0.*",
+                "symfony/http-foundation": "2.8.*|3.0.*",
+                "symfony/http-kernel": "2.8.*|3.0.*",
+                "symfony/polyfill-php56": "~1.0",
+                "symfony/process": "2.8.*|3.0.*",
+                "symfony/routing": "2.8.*|3.0.*",
+                "symfony/translation": "2.8.*|3.0.*",
+                "symfony/var-dumper": "2.8.*|3.0.*",
+                "vlucas/phpdotenv": "~2.2"
+            },
+            "replace": {
+                "illuminate/auth": "self.version",
+                "illuminate/broadcasting": "self.version",
+                "illuminate/bus": "self.version",
+                "illuminate/cache": "self.version",
+                "illuminate/config": "self.version",
+                "illuminate/console": "self.version",
+                "illuminate/container": "self.version",
+                "illuminate/contracts": "self.version",
+                "illuminate/cookie": "self.version",
+                "illuminate/database": "self.version",
+                "illuminate/encryption": "self.version",
+                "illuminate/events": "self.version",
+                "illuminate/exception": "self.version",
+                "illuminate/filesystem": "self.version",
+                "illuminate/hashing": "self.version",
+                "illuminate/http": "self.version",
+                "illuminate/log": "self.version",
+                "illuminate/mail": "self.version",
+                "illuminate/pagination": "self.version",
+                "illuminate/pipeline": "self.version",
+                "illuminate/queue": "self.version",
+                "illuminate/redis": "self.version",
+                "illuminate/routing": "self.version",
+                "illuminate/session": "self.version",
+                "illuminate/support": "self.version",
+                "illuminate/translation": "self.version",
+                "illuminate/validation": "self.version",
+                "illuminate/view": "self.version"
+            },
+            "require-dev": {
+                "aws/aws-sdk-php": "~3.0",
+                "mockery/mockery": "~0.9.4",
+                "pda/pheanstalk": "~3.0",
+                "phpunit/phpunit": "~4.1",
+                "predis/predis": "~1.0",
+                "symfony/css-selector": "2.8.*|3.0.*",
+                "symfony/dom-crawler": "2.8.*|3.0.*"
+            },
+            "suggest": {
+                "aws/aws-sdk-php": "Required to use the SQS queue driver and SES mail driver (~3.0).",
+                "doctrine/dbal": "Required to rename columns and drop SQLite columns (~2.4).",
+                "fzaninotto/faker": "Required to use the eloquent factory builder (~1.4).",
+                "guzzlehttp/guzzle": "Required to use the Mailgun and Mandrill mail drivers and the ping methods on schedules (~5.3|~6.0).",
+                "league/flysystem-aws-s3-v3": "Required to use the Flysystem S3 driver (~1.0).",
+                "league/flysystem-rackspace": "Required to use the Flysystem Rackspace driver (~1.0).",
+                "pda/pheanstalk": "Required to use the beanstalk queue driver (~3.0).",
+                "predis/predis": "Required to use the redis cache and queue drivers (~1.0).",
+                "pusher/pusher-php-server": "Required to use the Pusher broadcast driver (~2.0).",
+                "symfony/css-selector": "Required to use some of the crawler integration testing tools (2.8.*|3.0.*).",
+                "symfony/dom-crawler": "Required to use most of the crawler integration testing tools (2.8.*|3.0.*).",
+                "symfony/psr-http-message-bridge": "Required to psr7 bridging features (0.2.*)."
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "5.2-dev"
+                }
+            },
+            "autoload": {
+                "classmap": [
+                    "src/Illuminate/Queue/IlluminateQueueClosure.php"
+                ],
+                "files": [
+                    "src/Illuminate/Foundation/helpers.php",
+                    "src/Illuminate/Support/helpers.php"
+                ],
+                "psr-4": {
+                    "Illuminate\\": "src/Illuminate/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Taylor Otwell",
+                    "email": "taylorotwell@gmail.com"
+                }
+            ],
+            "description": "The Laravel Framework.",
+            "homepage": "http://laravel.com",
+            "keywords": [
+                "framework",
+                "laravel"
+            ],
+            "time": "2016-05-17 13:24:40"
+        },
+        {
+            "name": "lcobucci/jwt",
+            "version": "3.1.1",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/lcobucci/jwt.git",
+                "reference": "afea8e682e911a21574fd8519321b32522fa25b5"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/lcobucci/jwt/zipball/afea8e682e911a21574fd8519321b32522fa25b5",
+                "reference": "afea8e682e911a21574fd8519321b32522fa25b5",
+                "shasum": ""
+            },
+            "require": {
+                "ext-openssl": "*",
+                "php": ">=5.5"
+            },
+            "require-dev": {
+                "mdanter/ecc": "~0.3",
+                "mikey179/vfsstream": "~1.5",
+                "phpmd/phpmd": "~2.2",
+                "phpunit/php-invoker": "~1.1",
+                "phpunit/phpunit": "~4.5",
+                "squizlabs/php_codesniffer": "~2.3"
+            },
+            "suggest": {
+                "mdanter/ecc": "Required to use Elliptic Curves based algorithms."
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "3.1-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Lcobucci\\JWT\\": "src"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-3-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Luís Otávio Cobucci Oblonczyk",
+                    "email": "lcobucci@gmail.com",
+                    "role": "Developer"
+                }
+            ],
+            "description": "A simple library to work with JSON Web Token and JSON Web Signature",
+            "keywords": [
+                "JWS",
+                "jwt"
+            ],
+            "time": "2016-03-24 22:46:13"
+        },
+        {
+            "name": "league/commonmark",
+            "version": "0.13.3",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/thephpleague/commonmark.git",
+                "reference": "35816f39eb2498484fbb7b1495633a976ee1a8de"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/thephpleague/commonmark/zipball/35816f39eb2498484fbb7b1495633a976ee1a8de",
+                "reference": "35816f39eb2498484fbb7b1495633a976ee1a8de",
+                "shasum": ""
+            },
+            "require": {
+                "ext-mbstring": "*",
+                "php": ">=5.4.8"
+            },
+            "replace": {
+                "colinodell/commonmark-php": "*"
+            },
+            "require-dev": {
+                "cebe/markdown": "~1.0",
+                "erusev/parsedown": "~1.0",
+                "jgm/commonmark": "0.25",
+                "michelf/php-markdown": "~1.4",
+                "mikehaertl/php-shellcommand": "~1.2.0",
+                "phpunit/phpunit": "~4.3|~5.0",
+                "scrutinizer/ocular": "~1.1",
+                "symfony/finder": "~2.3|~3.0"
+            },
+            "suggest": {
+                "league/commonmark-extras": "Library of useful extensions including smart punctuation"
+            },
+            "bin": [
+                "bin/commonmark"
+            ],
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "0.14-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "League\\CommonMark\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-3-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Colin O'Dell",
+                    "email": "colinodell@gmail.com",
+                    "homepage": "https://www.colinodell.com",
+                    "role": "Lead Developer"
+                }
+            ],
+            "description": "Markdown parser for PHP based on the CommonMark spec",
+            "homepage": "https://github.com/thephpleague/commonmark",
+            "keywords": [
+                "commonmark",
+                "markdown",
+                "parser"
+            ],
+            "time": "2016-05-21 18:41:30"
+        },
+        {
+            "name": "league/flysystem",
+            "version": "1.0.22",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/thephpleague/flysystem.git",
+                "reference": "bd73a91703969a2d20ab4bfbf971d6c2cbe36612"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/bd73a91703969a2d20ab4bfbf971d6c2cbe36612",
+                "reference": "bd73a91703969a2d20ab4bfbf971d6c2cbe36612",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.4.0"
+            },
+            "conflict": {
+                "league/flysystem-sftp": "<1.0.6"
+            },
+            "require-dev": {
+                "ext-fileinfo": "*",
+                "mockery/mockery": "~0.9",
+                "phpspec/phpspec": "^2.2",
+                "phpunit/phpunit": "~4.8 || ~5.0"
+            },
+            "suggest": {
+                "ext-fileinfo": "Required for MimeType",
+                "league/flysystem-aws-s3-v2": "Allows you to use S3 storage with AWS SDK v2",
+                "league/flysystem-aws-s3-v3": "Allows you to use S3 storage with AWS SDK v3",
+                "league/flysystem-azure": "Allows you to use Windows Azure Blob storage",
+                "league/flysystem-cached-adapter": "Flysystem adapter decorator for metadata caching",
+                "league/flysystem-copy": "Allows you to use Copy.com storage",
+                "league/flysystem-dropbox": "Allows you to use Dropbox storage",
+                "league/flysystem-eventable-filesystem": "Allows you to use EventableFilesystem",
+                "league/flysystem-rackspace": "Allows you to use Rackspace Cloud Files",
+                "league/flysystem-sftp": "Allows you to use SFTP server storage via phpseclib",
+                "league/flysystem-webdav": "Allows you to use WebDAV storage",
+                "league/flysystem-ziparchive": "Allows you to use ZipArchive adapter"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.1-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "League\\Flysystem\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Frank de Jonge",
+                    "email": "info@frenky.net"
+                }
+            ],
+            "description": "Filesystem abstraction: Many filesystems, one API.",
+            "keywords": [
+                "Cloud Files",
+                "WebDAV",
+                "abstraction",
+                "aws",
+                "cloud",
+                "copy.com",
+                "dropbox",
+                "file systems",
+                "files",
+                "filesystem",
+                "filesystems",
+                "ftp",
+                "rackspace",
+                "remote",
+                "s3",
+                "sftp",
+                "storage"
+            ],
+            "time": "2016-04-28 06:53:12"
+        },
+        {
+            "name": "league/flysystem-aws-s3-v3",
+            "version": "1.0.11",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/thephpleague/flysystem-aws-s3-v3.git",
+                "reference": "1f7ae4e3cc178686c49a9d23cab43ed1e955368c"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/thephpleague/flysystem-aws-s3-v3/zipball/1f7ae4e3cc178686c49a9d23cab43ed1e955368c",
+                "reference": "1f7ae4e3cc178686c49a9d23cab43ed1e955368c",
+                "shasum": ""
+            },
+            "require": {
+                "aws/aws-sdk-php": "^3.0.0",
+                "league/flysystem": "~1.0",
+                "php": ">=5.5.0"
+            },
+            "require-dev": {
+                "henrikbjorn/phpspec-code-coverage": "~1.0.1",
+                "phpspec/phpspec": "^2.0.0"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.0-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "League\\Flysystem\\AwsS3v3\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Frank de Jonge",
+                    "email": "info@frenky.net"
+                }
+            ],
+            "description": "Flysystem adapter for the AWS S3 SDK v3.x",
+            "time": "2016-05-03 19:35:35"
+        },
+        {
+            "name": "league/glide",
+            "version": "0.3.6",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/thephpleague/glide.git",
+                "reference": "e45a4b536924956e1b20f5d023800557d466eda7"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/thephpleague/glide/zipball/e45a4b536924956e1b20f5d023800557d466eda7",
+                "reference": "e45a4b536924956e1b20f5d023800557d466eda7",
+                "shasum": ""
+            },
+            "require": {
+                "intervention/image": "~2.1",
+                "league/flysystem": "~1.0",
+                "php": ">=5.4",
+                "symfony/http-foundation": "~2.3|~3.0",
+                "symfony/http-kernel": "~2.3|~3.0"
+            },
+            "require-dev": {
+                "mockery/mockery": "~0.9",
+                "phpunit/php-token-stream": ">=1.3.0",
+                "phpunit/phpunit": "~4.4"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "0.3-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "League\\Glide\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Jonathan Reinink",
+                    "email": "jonathan@reinink.ca",
+                    "homepage": "http://reinink.ca"
+                }
+            ],
+            "description": "Wonderfully easy on-demand image manipulation library with an HTTP based API.",
+            "homepage": "https://github.com/thephpleague/glide",
+            "keywords": [
+                "ImageMagick",
+                "editing",
+                "gd",
+                "image",
+                "imagick",
+                "league",
+                "manipulation",
+                "processing"
+            ],
+            "time": "2016-01-25 13:35:12"
+        },
+        {
+            "name": "martinbean/laravel-sluggable-trait",
+            "version": "0.2.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/martinbean/laravel-sluggable-trait.git",
+                "reference": "8984dc9bc2596814f79baf44aeb9a39c9c07b149"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/martinbean/laravel-sluggable-trait/zipball/8984dc9bc2596814f79baf44aeb9a39c9c07b149",
+                "reference": "8984dc9bc2596814f79baf44aeb9a39c9c07b149",
+                "shasum": ""
+            },
+            "require": {
+                "illuminate/database": ">=4.0",
+                "illuminate/support": ">=4.0",
+                "php": ">=5.4.0"
+            },
+            "type": "library",
+            "autoload": {
+                "psr-4": {
+                    "MartinBean\\Database\\Eloquent\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Martin Bean",
+                    "email": "martin@martinbean.co.uk"
+                }
+            ],
+            "description": "A trait you can apply to Eloquent models to have slugs automatically generated on save.",
+            "keywords": [
+                "eloquent",
+                "laravel"
+            ],
+            "time": "2015-02-17 22:47:44"
+        },
+        {
+            "name": "mf2/mf2",
+            "version": "v0.3.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/indieweb/php-mf2.git",
+                "reference": "4fb2eb5365cbc0fd2e0c26ca748777d6c2539763"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/indieweb/php-mf2/zipball/4fb2eb5365cbc0fd2e0c26ca748777d6c2539763",
+                "reference": "4fb2eb5365cbc0fd2e0c26ca748777d6c2539763",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.4.0"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "3.7.*"
+            },
+            "suggest": {
+                "barnabywalters/mf-cleaner": "To more easily handle the canonical data php-mf2 gives you"
+            },
+            "bin": [
+                "bin/fetch-mf2",
+                "bin/parse-mf2"
+            ],
+            "type": "library",
+            "autoload": {
+                "files": [
+                    "Mf2/Parser.php"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "CC0"
+            ],
+            "authors": [
+                {
+                    "name": "Barnaby Walters",
+                    "homepage": "http://waterpigs.co.uk"
+                }
+            ],
+            "description": "A pure, generic microformats2 parser — makes HTML as easy to consume as a JSON API",
+            "keywords": [
+                "html",
+                "microformats",
+                "microformats 2",
+                "parser",
+                "semantic"
+            ],
+            "time": "2016-03-14 12:13:34"
+        },
+        {
+            "name": "monolog/monolog",
+            "version": "1.19.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/Seldaek/monolog.git",
+                "reference": "5f56ed5212dc509c8dc8caeba2715732abb32dbf"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/Seldaek/monolog/zipball/5f56ed5212dc509c8dc8caeba2715732abb32dbf",
+                "reference": "5f56ed5212dc509c8dc8caeba2715732abb32dbf",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.3.0",
+                "psr/log": "~1.0"
+            },
+            "provide": {
+                "psr/log-implementation": "1.0.0"
+            },
+            "require-dev": {
+                "aws/aws-sdk-php": "^2.4.9",
+                "doctrine/couchdb": "~1.0@dev",
+                "graylog2/gelf-php": "~1.0",
+                "jakub-onderka/php-parallel-lint": "0.9",
+                "php-amqplib/php-amqplib": "~2.4",
+                "php-console/php-console": "^3.1.3",
+                "phpunit/phpunit": "~4.5",
+                "phpunit/phpunit-mock-objects": "2.3.0",
+                "raven/raven": "^0.13",
+                "ruflin/elastica": ">=0.90 <3.0",
+                "swiftmailer/swiftmailer": "~5.3"
+            },
+            "suggest": {
+                "aws/aws-sdk-php": "Allow sending log messages to AWS services like DynamoDB",
+                "doctrine/couchdb": "Allow sending log messages to a CouchDB server",
+                "ext-amqp": "Allow sending log messages to an AMQP server (1.0+ required)",
+                "ext-mongo": "Allow sending log messages to a MongoDB server",
+                "graylog2/gelf-php": "Allow sending log messages to a GrayLog2 server",
+                "mongodb/mongodb": "Allow sending log messages to a MongoDB server via PHP Driver",
+                "php-amqplib/php-amqplib": "Allow sending log messages to an AMQP server using php-amqplib",
+                "php-console/php-console": "Allow sending log messages to Google Chrome",
+                "raven/raven": "Allow sending log messages to a Sentry server",
+                "rollbar/rollbar": "Allow sending log messages to Rollbar",
+                "ruflin/elastica": "Allow sending log messages to an Elastic Search server"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "2.0.x-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Monolog\\": "src/Monolog"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Jordi Boggiano",
+                    "email": "j.boggiano@seld.be",
+                    "homepage": "http://seld.be"
+                }
+            ],
+            "description": "Sends your logs to files, sockets, inboxes, databases and various web services",
+            "homepage": "http://github.com/Seldaek/monolog",
+            "keywords": [
+                "log",
+                "logging",
+                "psr-3"
+            ],
+            "time": "2016-04-12 18:29:35"
+        },
+        {
+            "name": "mtdowling/cron-expression",
+            "version": "v1.1.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/mtdowling/cron-expression.git",
+                "reference": "c9ee7886f5a12902b225a1a12f36bb45f9ab89e5"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/mtdowling/cron-expression/zipball/c9ee7886f5a12902b225a1a12f36bb45f9ab89e5",
+                "reference": "c9ee7886f5a12902b225a1a12f36bb45f9ab89e5",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.3.2"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "~4.0|~5.0"
+            },
+            "type": "library",
+            "autoload": {
+                "psr-0": {
+                    "Cron": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Michael Dowling",
+                    "email": "mtdowling@gmail.com",
+                    "homepage": "https://github.com/mtdowling"
+                }
+            ],
+            "description": "CRON for PHP: Calculate the next or previous run date and determine if a CRON expression is due",
+            "keywords": [
+                "cron",
+                "schedule"
+            ],
+            "time": "2016-01-26 21:23:30"
+        },
+        {
+            "name": "mtdowling/jmespath.php",
+            "version": "2.3.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/jmespath/jmespath.php.git",
+                "reference": "192f93e43c2c97acde7694993ab171b3de284093"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/jmespath/jmespath.php/zipball/192f93e43c2c97acde7694993ab171b3de284093",
+                "reference": "192f93e43c2c97acde7694993ab171b3de284093",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.4.0"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "~4.0"
+            },
+            "bin": [
+                "bin/jp.php"
+            ],
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "2.0-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "JmesPath\\": "src/"
+                },
+                "files": [
+                    "src/JmesPath.php"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Michael Dowling",
+                    "email": "mtdowling@gmail.com",
+                    "homepage": "https://github.com/mtdowling"
+                }
+            ],
+            "description": "Declaratively specify how to extract elements from a JSON document",
+            "keywords": [
+                "json",
+                "jsonpath"
+            ],
+            "time": "2016-01-05 18:25:05"
+        },
+        {
+            "name": "nesbot/carbon",
+            "version": "1.21.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/briannesbitt/Carbon.git",
+                "reference": "7b08ec6f75791e130012f206e3f7b0e76e18e3d7"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/briannesbitt/Carbon/zipball/7b08ec6f75791e130012f206e3f7b0e76e18e3d7",
+                "reference": "7b08ec6f75791e130012f206e3f7b0e76e18e3d7",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.3.0",
+                "symfony/translation": "~2.6|~3.0"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "~4.0|~5.0"
+            },
+            "type": "library",
+            "autoload": {
+                "psr-4": {
+                    "Carbon\\": "src/Carbon/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Brian Nesbitt",
+                    "email": "brian@nesbot.com",
+                    "homepage": "http://nesbot.com"
+                }
+            ],
+            "description": "A simple API extension for DateTime.",
+            "homepage": "http://carbon.nesbot.com",
+            "keywords": [
+                "date",
+                "datetime",
+                "time"
+            ],
+            "time": "2015-11-04 20:07:17"
+        },
+        {
+            "name": "nikic/php-parser",
+            "version": "v2.1.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/nikic/PHP-Parser.git",
+                "reference": "47b254ea51f1d6d5dc04b9b299e88346bf2369e3"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/47b254ea51f1d6d5dc04b9b299e88346bf2369e3",
+                "reference": "47b254ea51f1d6d5dc04b9b299e88346bf2369e3",
+                "shasum": ""
+            },
+            "require": {
+                "ext-tokenizer": "*",
+                "php": ">=5.4"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "~4.0"
+            },
+            "bin": [
+                "bin/php-parse"
+            ],
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "2.1-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "PhpParser\\": "lib/PhpParser"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-3-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Nikita Popov"
+                }
+            ],
+            "description": "A PHP parser written in PHP",
+            "keywords": [
+                "parser",
+                "php"
+            ],
+            "time": "2016-04-19 13:41:41"
+        },
+        {
+            "name": "paragonie/random_compat",
+            "version": "v1.4.1",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/paragonie/random_compat.git",
+                "reference": "c7e26a21ba357863de030f0b9e701c7d04593774"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/paragonie/random_compat/zipball/c7e26a21ba357863de030f0b9e701c7d04593774",
+                "reference": "c7e26a21ba357863de030f0b9e701c7d04593774",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.2.0"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "4.*|5.*"
+            },
+            "suggest": {
+                "ext-libsodium": "Provides a modern crypto API that can be used to generate random bytes."
+            },
+            "type": "library",
+            "autoload": {
+                "files": [
+                    "lib/random.php"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Paragon Initiative Enterprises",
+                    "email": "security@paragonie.com",
+                    "homepage": "https://paragonie.com"
+                }
+            ],
+            "description": "PHP 5.x polyfill for random_bytes() and random_int() from PHP 7",
+            "keywords": [
+                "csprng",
+                "pseudorandom",
+                "random"
+            ],
+            "time": "2016-03-18 20:34:03"
+        },
+        {
+            "name": "patchwork/utf8",
+            "version": "v1.3.1",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/tchwork/utf8.git",
+                "reference": "30ec6451aec7d2536f0af8fe535f70c764f2c47a"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/tchwork/utf8/zipball/30ec6451aec7d2536f0af8fe535f70c764f2c47a",
+                "reference": "30ec6451aec7d2536f0af8fe535f70c764f2c47a",
+                "shasum": ""
+            },
+            "require": {
+                "lib-pcre": ">=7.3",
+                "php": ">=5.3.0"
+            },
+            "suggest": {
+                "ext-iconv": "Use iconv for best performance",
+                "ext-intl": "Use Intl for best performance",
+                "ext-mbstring": "Use Mbstring for best performance",
+                "ext-wfio": "Use WFIO for UTF-8 filesystem access on Windows"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.3-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Patchwork\\": "src/Patchwork/"
+                },
+                "classmap": [
+                    "src/Normalizer.php"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "(Apache-2.0 or GPL-2.0)"
+            ],
+            "authors": [
+                {
+                    "name": "Nicolas Grekas",
+                    "email": "p@tchwork.com"
+                }
+            ],
+            "description": "Portable and performant UTF-8, Unicode and Grapheme Clusters for PHP",
+            "homepage": "https://github.com/tchwork/utf8",
+            "keywords": [
+                "grapheme",
+                "i18n",
+                "unicode",
+                "utf-8",
+                "utf8"
+            ],
+            "time": "2016-05-18 13:57:10"
+        },
+        {
+            "name": "phaza/laravel-postgis",
+            "version": "dev-master",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/njbarrett/laravel-postgis.git",
+                "reference": "accec379af8ceba903ceb10df37beeb9bfb411cc"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/njbarrett/laravel-postgis/zipball/accec379af8ceba903ceb10df37beeb9bfb411cc",
+                "reference": "accec379af8ceba903ceb10df37beeb9bfb411cc",
+                "shasum": ""
+            },
+            "require": {
+                "bosnadev/database": "~0.16",
+                "geo-io/wkb-parser": "^1.0",
+                "illuminate/database": "^5.2",
+                "jmikola/geojson": "^1.0",
+                "php": ">=5.5"
+            },
+            "require-dev": {
+                "codeclimate/php-test-reporter": "~0.3",
+                "illuminate/pagination": "~5.0",
+                "mockery/mockery": "0.9.*",
+                "phpunit/phpunit": "~4.5"
+            },
+            "type": "library",
+            "autoload": {
+                "psr-4": {
+                    "Phaza\\LaravelPostgis\\": "src/"
+                }
+            },
+            "autoload-dev": {
+                "classmap": [
+                    "tests/BaseTestCase.php",
+                    "tests/Stubs/"
+                ]
+            },
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Peter Haza",
+                    "email": "peter.haza@gmail.com"
+                },
+                {
+                    "name": "Nicholas Barrett",
+                    "email": "njbarrett7@gmail.com"
+                }
+            ],
+            "description": "Postgis extensions for laravel. Aims to make it easy to work with geometries from laravel models",
+            "support": {
+                "source": "https://github.com/njbarrett/laravel-postgis/tree/master",
+                "issues": "https://github.com/njbarrett/laravel-postgis/issues"
+            },
+            "time": "2016-05-21 08:00:18"
+        },
+        {
+            "name": "predis/predis",
+            "version": "v1.0.3",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/nrk/predis.git",
+                "reference": "84060b9034d756b4d79641667d7f9efe1aeb8e04"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/nrk/predis/zipball/84060b9034d756b4d79641667d7f9efe1aeb8e04",
+                "reference": "84060b9034d756b4d79641667d7f9efe1aeb8e04",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.3.2"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "~4.0"
+            },
+            "suggest": {
+                "ext-curl": "Allows access to Webdis when paired with phpiredis",
+                "ext-phpiredis": "Allows faster serialization and deserialization of the Redis protocol"
+            },
+            "type": "library",
+            "autoload": {
+                "psr-4": {
+                    "Predis\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Daniele Alessandri",
+                    "email": "suppakilla@gmail.com",
+                    "homepage": "http://clorophilla.net"
+                }
+            ],
+            "description": "Flexible and feature-complete PHP client library for Redis",
+            "homepage": "http://github.com/nrk/predis",
+            "keywords": [
+                "nosql",
+                "predis",
+                "redis"
+            ],
+            "time": "2015-07-30 18:34:15"
+        },
+        {
+            "name": "psr/http-message",
+            "version": "1.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/php-fig/http-message.git",
+                "reference": "85d63699f0dbedb190bbd4b0d2b9dc707ea4c298"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/php-fig/http-message/zipball/85d63699f0dbedb190bbd4b0d2b9dc707ea4c298",
+                "reference": "85d63699f0dbedb190bbd4b0d2b9dc707ea4c298",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.3.0"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.0.x-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Psr\\Http\\Message\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "PHP-FIG",
+                    "homepage": "http://www.php-fig.org/"
+                }
+            ],
+            "description": "Common interface for HTTP messages",
+            "keywords": [
+                "http",
+                "http-message",
+                "psr",
+                "psr-7",
+                "request",
+                "response"
+            ],
+            "time": "2015-05-04 20:22:00"
+        },
+        {
+            "name": "psr/log",
+            "version": "1.0.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/php-fig/log.git",
+                "reference": "fe0936ee26643249e916849d48e3a51d5f5e278b"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/php-fig/log/zipball/fe0936ee26643249e916849d48e3a51d5f5e278b",
+                "reference": "fe0936ee26643249e916849d48e3a51d5f5e278b",
+                "shasum": ""
+            },
+            "type": "library",
+            "autoload": {
+                "psr-0": {
+                    "Psr\\Log\\": ""
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "PHP-FIG",
+                    "homepage": "http://www.php-fig.org/"
+                }
+            ],
+            "description": "Common interface for logging libraries",
+            "keywords": [
+                "log",
+                "psr",
+                "psr-3"
+            ],
+            "time": "2012-12-21 11:40:51"
+        },
+        {
+            "name": "psy/psysh",
+            "version": "v0.7.2",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/bobthecow/psysh.git",
+                "reference": "e64e10b20f8d229cac76399e1f3edddb57a0f280"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/bobthecow/psysh/zipball/e64e10b20f8d229cac76399e1f3edddb57a0f280",
+                "reference": "e64e10b20f8d229cac76399e1f3edddb57a0f280",
+                "shasum": ""
+            },
+            "require": {
+                "dnoegel/php-xdg-base-dir": "0.1",
+                "jakub-onderka/php-console-highlighter": "0.3.*",
+                "nikic/php-parser": "^1.2.1|~2.0",
+                "php": ">=5.3.9",
+                "symfony/console": "~2.3.10|^2.4.2|~3.0",
+                "symfony/var-dumper": "~2.7|~3.0"
+            },
+            "require-dev": {
+                "fabpot/php-cs-fixer": "~1.5",
+                "phpunit/phpunit": "~3.7|~4.0|~5.0",
+                "squizlabs/php_codesniffer": "~2.0",
+                "symfony/finder": "~2.1|~3.0"
+            },
+            "suggest": {
+                "ext-pcntl": "Enabling the PCNTL extension makes PsySH a lot happier :)",
+                "ext-pdo-sqlite": "The doc command requires SQLite to work.",
+                "ext-posix": "If you have PCNTL, you'll want the POSIX extension as well.",
+                "ext-readline": "Enables support for arrow-key history navigation, and showing and manipulating command history."
+            },
+            "bin": [
+                "bin/psysh"
+            ],
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-develop": "0.8.x-dev"
+                }
+            },
+            "autoload": {
+                "files": [
+                    "src/Psy/functions.php"
+                ],
+                "psr-4": {
+                    "Psy\\": "src/Psy/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Justin Hileman",
+                    "email": "justin@justinhileman.info",
+                    "homepage": "http://justinhileman.com"
+                }
+            ],
+            "description": "An interactive shell for modern PHP.",
+            "homepage": "http://psysh.org",
+            "keywords": [
+                "REPL",
+                "console",
+                "interactive",
+                "shell"
+            ],
+            "time": "2016-03-09 05:03:14"
+        },
+        {
+            "name": "ramsey/uuid",
+            "version": "3.4.1",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/ramsey/uuid.git",
+                "reference": "b4fe3b7387cb323fd15ad5837cae992422c9fa5c"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/ramsey/uuid/zipball/b4fe3b7387cb323fd15ad5837cae992422c9fa5c",
+                "reference": "b4fe3b7387cb323fd15ad5837cae992422c9fa5c",
+                "shasum": ""
+            },
+            "require": {
+                "paragonie/random_compat": "^1.0|^2.0",
+                "php": ">=5.4"
+            },
+            "replace": {
+                "rhumsaa/uuid": "self.version"
+            },
+            "require-dev": {
+                "apigen/apigen": "^4.1",
+                "codeception/aspect-mock": "1.0.0",
+                "goaop/framework": "1.0.0-alpha.2",
+                "ircmaxell/random-lib": "^1.1",
+                "jakub-onderka/php-parallel-lint": "^0.9.0",
+                "mockery/mockery": "^0.9.4",
+                "moontoast/math": "^1.1",
+                "phpunit/phpunit": "^4.7|^5.0",
+                "satooshi/php-coveralls": "^0.6.1",
+                "squizlabs/php_codesniffer": "^2.3"
+            },
+            "suggest": {
+                "ext-libsodium": "Provides the PECL libsodium extension for use with the SodiumRandomGenerator",
+                "ext-uuid": "Provides the PECL UUID extension for use with the PeclUuidTimeGenerator and PeclUuidRandomGenerator",
+                "ircmaxell/random-lib": "Provides RandomLib for use with the RandomLibAdapter",
+                "moontoast/math": "Provides support for converting UUID to 128-bit integer (in string form).",
+                "ramsey/uuid-console": "A console application for generating UUIDs with ramsey/uuid",
+                "ramsey/uuid-doctrine": "Allows the use of Ramsey\\Uuid\\Uuid as Doctrine field type."
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "3.x-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Ramsey\\Uuid\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Marijn Huizendveld",
+                    "email": "marijn.huizendveld@gmail.com"
+                },
+                {
+                    "name": "Thibaud Fabre",
+                    "email": "thibaud@aztech.io"
+                },
+                {
+                    "name": "Ben Ramsey",
+                    "email": "ben@benramsey.com",
+                    "homepage": "https://benramsey.com"
+                }
+            ],
+            "description": "Formerly rhumsaa/uuid. A PHP 5.4+ library for generating RFC 4122 version 1, 3, 4, and 5 universally unique identifiers (UUID).",
+            "homepage": "https://github.com/ramsey/uuid",
+            "keywords": [
+                "guid",
+                "identifier",
+                "uuid"
+            ],
+            "time": "2016-04-24 00:30:41"
+        },
+        {
+            "name": "spatie/laravel-glide",
+            "version": "2.3.3",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/spatie/laravel-glide.git",
+                "reference": "06bcfb85464a1202dfaa7494d1b1600c88c418e5"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/spatie/laravel-glide/zipball/06bcfb85464a1202dfaa7494d1b1600c88c418e5",
+                "reference": "06bcfb85464a1202dfaa7494d1b1600c88c418e5",
+                "shasum": ""
+            },
+            "require": {
+                "illuminate/support": "5.*",
+                "league/glide": "0.3.*",
+                "php": ">=5.4.0"
+            },
+            "require-dev": {
+                "codeception/codeception": "2.*",
+                "mockery/mockery": "~0.9.3"
+            },
+            "type": "library",
+            "autoload": {
+                "psr-0": {
+                    "Spatie\\Glide": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Freek Van der Herten",
+                    "email": "freek@spatie.be"
+                }
+            ],
+            "description": "A Glide Service Provider for Laravel",
+            "homepage": "https://github.com/spatie/laravel-glide",
+            "time": "2016-05-13 14:56:07"
+        },
+        {
+            "name": "spatie/laravel-medialibrary",
+            "version": "3.17.4",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/spatie/laravel-medialibrary.git",
+                "reference": "7f96cd3c709643ab0dde977936b98a7f75396d31"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/spatie/laravel-medialibrary/zipball/7f96cd3c709643ab0dde977936b98a7f75396d31",
+                "reference": "7f96cd3c709643ab0dde977936b98a7f75396d31",
+                "shasum": ""
+            },
+            "require": {
+                "illuminate/bus": "~5.1.16|~5.2.0",
+                "illuminate/console": "~5.1.16|~5.2.0",
+                "illuminate/database": "~5.1.16|~5.2.0",
+                "illuminate/support": "~5.1.16|~5.2.0",
+                "php": "^5.5|^7.0",
+                "spatie/laravel-glide": "^2.2.4",
+                "spatie/pdf-to-image": "^1.0.1",
+                "spatie/string": "^2.0"
+            },
+            "require-dev": {
+                "doctrine/dbal": "^2.5.2",
+                "mockery/mockery": "^0.9.4",
+                "orchestra/testbench": "^3.0",
+                "phpunit/phpunit": "^4.0",
+                "scrutinizer/ocular": "^1.1"
+            },
+            "type": "library",
+            "autoload": {
+                "psr-4": {
+                    "Spatie\\MediaLibrary\\": "src"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Freek Van der Herten",
+                    "email": "freek@spatie.be",
+                    "homepage": "https://murze.be",
+                    "role": "Developer"
+                }
+            ],
+            "description": "Associate files with Eloquent models",
+            "homepage": "https://github.com/spatie/laravel-medialibrary",
+            "keywords": [
+                "cms",
+                "laravel",
+                "laravel-medialibrary",
+                "media",
+                "spatie"
+            ],
+            "time": "2016-04-12 09:57:52"
+        },
+        {
+            "name": "spatie/pdf-to-image",
+            "version": "1.2.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/spatie/pdf-to-image.git",
+                "reference": "c08dac65f0f857dd4d467d40794772be5a75d6de"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/spatie/pdf-to-image/zipball/c08dac65f0f857dd4d467d40794772be5a75d6de",
+                "reference": "c08dac65f0f857dd4d467d40794772be5a75d6de",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.5.0"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "4.*",
+                "scrutinizer/ocular": "~1.1"
+            },
+            "type": "library",
+            "autoload": {
+                "psr-4": {
+                    "Spatie\\PdfToImage\\": "src"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Freek Van der Herten",
+                    "email": "freek@spatie.be",
+                    "homepage": "https://spatie.be",
+                    "role": "Developer"
+                }
+            ],
+            "description": "Convert a pdf to an image",
+            "homepage": "https://github.com/spatie/pdf-to-image",
+            "keywords": [
+                "convert",
+                "image",
+                "pdf",
+                "pdf-to-image",
+                "spatie"
+            ],
+            "time": "2016-04-29 08:02:56"
+        },
+        {
+            "name": "spatie/string",
+            "version": "2.1.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/spatie/string.git",
+                "reference": "1843189c711be4dcf62b655824f2f17120c2dc7d"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/spatie/string/zipball/1843189c711be4dcf62b655824f2f17120c2dc7d",
+                "reference": "1843189c711be4dcf62b655824f2f17120c2dc7d",
+                "shasum": ""
+            },
+            "require": {
+                "anahkiasen/underscore-php": "^2.0",
+                "php": ">=5.5.0"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "4.*",
+                "scrutinizer/ocular": "~1.1"
+            },
+            "type": "library",
+            "autoload": {
+                "files": [
+                    "src/string_functions.php"
+                ],
+                "psr-4": {
+                    "Spatie\\String\\": "src"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Freek Van der Herten",
+                    "email": "freek@spatie.be",
+                    "homepage": "https://murze.be",
+                    "role": "Developer"
+                }
+            ],
+            "description": "String handling evolved",
+            "homepage": "https://github.com/spatie/string",
+            "keywords": [
+                "handling",
+                "handy",
+                "spatie",
+                "string"
+            ],
+            "time": "2015-11-02 13:00:37"
+        },
+        {
+            "name": "swiftmailer/swiftmailer",
+            "version": "v5.4.2",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/swiftmailer/swiftmailer.git",
+                "reference": "d8db871a54619458a805229a057ea2af33c753e8"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/swiftmailer/swiftmailer/zipball/d8db871a54619458a805229a057ea2af33c753e8",
+                "reference": "d8db871a54619458a805229a057ea2af33c753e8",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.3.3"
+            },
+            "require-dev": {
+                "mockery/mockery": "~0.9.1,<0.9.4"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "5.4-dev"
+                }
+            },
+            "autoload": {
+                "files": [
+                    "lib/swift_required.php"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Chris Corbyn"
+                },
+                {
+                    "name": "Fabien Potencier",
+                    "email": "fabien@symfony.com"
+                }
+            ],
+            "description": "Swiftmailer, free feature-rich PHP mailer",
+            "homepage": "http://swiftmailer.org",
+            "keywords": [
+                "email",
+                "mail",
+                "mailer"
+            ],
+            "time": "2016-05-01 08:45:47"
+        },
+        {
+            "name": "symfony/console",
+            "version": "v3.0.6",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/symfony/console.git",
+                "reference": "34a214710e0714b6efcf40ba3cd1e31373a97820"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/symfony/console/zipball/34a214710e0714b6efcf40ba3cd1e31373a97820",
+                "reference": "34a214710e0714b6efcf40ba3cd1e31373a97820",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.5.9",
+                "symfony/polyfill-mbstring": "~1.0"
+            },
+            "require-dev": {
+                "psr/log": "~1.0",
+                "symfony/event-dispatcher": "~2.8|~3.0",
+                "symfony/process": "~2.8|~3.0"
+            },
+            "suggest": {
+                "psr/log": "For using the console logger",
+                "symfony/event-dispatcher": "",
+                "symfony/process": ""
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "3.0-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Symfony\\Component\\Console\\": ""
+                },
+                "exclude-from-classmap": [
+                    "/Tests/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Fabien Potencier",
+                    "email": "fabien@symfony.com"
+                },
+                {
+                    "name": "Symfony Community",
+                    "homepage": "https://symfony.com/contributors"
+                }
+            ],
+            "description": "Symfony Console Component",
+            "homepage": "https://symfony.com",
+            "time": "2016-04-28 09:48:42"
+        },
+        {
+            "name": "symfony/debug",
+            "version": "v3.0.6",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/symfony/debug.git",
+                "reference": "a06d10888a45afd97534506afb058ec38d9ba35b"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/symfony/debug/zipball/a06d10888a45afd97534506afb058ec38d9ba35b",
+                "reference": "a06d10888a45afd97534506afb058ec38d9ba35b",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.5.9",
+                "psr/log": "~1.0"
+            },
+            "conflict": {
+                "symfony/http-kernel": ">=2.3,<2.3.24|~2.4.0|>=2.5,<2.5.9|>=2.6,<2.6.2"
+            },
+            "require-dev": {
+                "symfony/class-loader": "~2.8|~3.0",
+                "symfony/http-kernel": "~2.8|~3.0"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "3.0-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Symfony\\Component\\Debug\\": ""
+                },
+                "exclude-from-classmap": [
+                    "/Tests/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Fabien Potencier",
+                    "email": "fabien@symfony.com"
+                },
+                {
+                    "name": "Symfony Community",
+                    "homepage": "https://symfony.com/contributors"
+                }
+            ],
+            "description": "Symfony Debug Component",
+            "homepage": "https://symfony.com",
+            "time": "2016-03-30 10:41:14"
+        },
+        {
+            "name": "symfony/event-dispatcher",
+            "version": "v3.0.6",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/symfony/event-dispatcher.git",
+                "reference": "807dde98589f9b2b00624dca326740380d78dbbc"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/807dde98589f9b2b00624dca326740380d78dbbc",
+                "reference": "807dde98589f9b2b00624dca326740380d78dbbc",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.5.9"
+            },
+            "require-dev": {
+                "psr/log": "~1.0",
+                "symfony/config": "~2.8|~3.0",
+                "symfony/dependency-injection": "~2.8|~3.0",
+                "symfony/expression-language": "~2.8|~3.0",
+                "symfony/stopwatch": "~2.8|~3.0"
+            },
+            "suggest": {
+                "symfony/dependency-injection": "",
+                "symfony/http-kernel": ""
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "3.0-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Symfony\\Component\\EventDispatcher\\": ""
+                },
+                "exclude-from-classmap": [
+                    "/Tests/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Fabien Potencier",
+                    "email": "fabien@symfony.com"
+                },
+                {
+                    "name": "Symfony Community",
+                    "homepage": "https://symfony.com/contributors"
+                }
+            ],
+            "description": "Symfony EventDispatcher Component",
+            "homepage": "https://symfony.com",
+            "time": "2016-05-05 06:56:13"
+        },
+        {
+            "name": "symfony/finder",
+            "version": "v3.0.6",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/symfony/finder.git",
+                "reference": "c54e407b35bc098916704e9fd090da21da4c4f52"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/symfony/finder/zipball/c54e407b35bc098916704e9fd090da21da4c4f52",
+                "reference": "c54e407b35bc098916704e9fd090da21da4c4f52",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.5.9"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "3.0-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Symfony\\Component\\Finder\\": ""
+                },
+                "exclude-from-classmap": [
+                    "/Tests/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Fabien Potencier",
+                    "email": "fabien@symfony.com"
+                },
+                {
+                    "name": "Symfony Community",
+                    "homepage": "https://symfony.com/contributors"
+                }
+            ],
+            "description": "Symfony Finder Component",
+            "homepage": "https://symfony.com",
+            "time": "2016-03-10 11:13:05"
+        },
+        {
+            "name": "symfony/http-foundation",
+            "version": "v3.0.6",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/symfony/http-foundation.git",
+                "reference": "18b24bc32d2495ae79d76e777368786a6536fe31"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/symfony/http-foundation/zipball/18b24bc32d2495ae79d76e777368786a6536fe31",
+                "reference": "18b24bc32d2495ae79d76e777368786a6536fe31",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.5.9",
+                "symfony/polyfill-mbstring": "~1.1"
+            },
+            "require-dev": {
+                "symfony/expression-language": "~2.8|~3.0"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "3.0-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Symfony\\Component\\HttpFoundation\\": ""
+                },
+                "exclude-from-classmap": [
+                    "/Tests/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Fabien Potencier",
+                    "email": "fabien@symfony.com"
+                },
+                {
+                    "name": "Symfony Community",
+                    "homepage": "https://symfony.com/contributors"
+                }
+            ],
+            "description": "Symfony HttpFoundation Component",
+            "homepage": "https://symfony.com",
+            "time": "2016-04-12 18:09:53"
+        },
+        {
+            "name": "symfony/http-kernel",
+            "version": "v3.0.6",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/symfony/http-kernel.git",
+                "reference": "6a5010978edf0a9646342232531e53bfc7abbcd3"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/symfony/http-kernel/zipball/6a5010978edf0a9646342232531e53bfc7abbcd3",
+                "reference": "6a5010978edf0a9646342232531e53bfc7abbcd3",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.5.9",
+                "psr/log": "~1.0",
+                "symfony/debug": "~2.8|~3.0",
+                "symfony/event-dispatcher": "~2.8|~3.0",
+                "symfony/http-foundation": "~2.8|~3.0"
+            },
+            "conflict": {
+                "symfony/config": "<2.8"
+            },
+            "require-dev": {
+                "symfony/browser-kit": "~2.8|~3.0",
+                "symfony/class-loader": "~2.8|~3.0",
+                "symfony/config": "~2.8|~3.0",
+                "symfony/console": "~2.8|~3.0",
+                "symfony/css-selector": "~2.8|~3.0",
+                "symfony/dependency-injection": "~2.8|~3.0",
+                "symfony/dom-crawler": "~2.8|~3.0",
+                "symfony/expression-language": "~2.8|~3.0",
+                "symfony/finder": "~2.8|~3.0",
+                "symfony/process": "~2.8|~3.0",
+                "symfony/routing": "~2.8|~3.0",
+                "symfony/stopwatch": "~2.8|~3.0",
+                "symfony/templating": "~2.8|~3.0",
+                "symfony/translation": "~2.8|~3.0",
+                "symfony/var-dumper": "~2.8|~3.0"
+            },
+            "suggest": {
+                "symfony/browser-kit": "",
+                "symfony/class-loader": "",
+                "symfony/config": "",
+                "symfony/console": "",
+                "symfony/dependency-injection": "",
+                "symfony/finder": "",
+                "symfony/var-dumper": ""
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "3.0-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Symfony\\Component\\HttpKernel\\": ""
+                },
+                "exclude-from-classmap": [
+                    "/Tests/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Fabien Potencier",
+                    "email": "fabien@symfony.com"
+                },
+                {
+                    "name": "Symfony Community",
+                    "homepage": "https://symfony.com/contributors"
+                }
+            ],
+            "description": "Symfony HttpKernel Component",
+            "homepage": "https://symfony.com",
+            "time": "2016-05-09 22:13:13"
+        },
+        {
+            "name": "symfony/polyfill-mbstring",
+            "version": "v1.2.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/symfony/polyfill-mbstring.git",
+                "reference": "dff51f72b0706335131b00a7f49606168c582594"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/dff51f72b0706335131b00a7f49606168c582594",
+                "reference": "dff51f72b0706335131b00a7f49606168c582594",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.3.3"
+            },
+            "suggest": {
+                "ext-mbstring": "For best performance"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.2-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Symfony\\Polyfill\\Mbstring\\": ""
+                },
+                "files": [
+                    "bootstrap.php"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Nicolas Grekas",
+                    "email": "p@tchwork.com"
+                },
+                {
+                    "name": "Symfony Community",
+                    "homepage": "https://symfony.com/contributors"
+                }
+            ],
+            "description": "Symfony polyfill for the Mbstring extension",
+            "homepage": "https://symfony.com",
+            "keywords": [
+                "compatibility",
+                "mbstring",
+                "polyfill",
+                "portable",
+                "shim"
+            ],
+            "time": "2016-05-18 14:26:46"
+        },
+        {
+            "name": "symfony/polyfill-php56",
+            "version": "v1.2.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/symfony/polyfill-php56.git",
+                "reference": "3edf57a8fbf9a927533344cef65ad7e1cf31030a"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/symfony/polyfill-php56/zipball/3edf57a8fbf9a927533344cef65ad7e1cf31030a",
+                "reference": "3edf57a8fbf9a927533344cef65ad7e1cf31030a",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.3.3",
+                "symfony/polyfill-util": "~1.0"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.2-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Symfony\\Polyfill\\Php56\\": ""
+                },
+                "files": [
+                    "bootstrap.php"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Nicolas Grekas",
+                    "email": "p@tchwork.com"
+                },
+                {
+                    "name": "Symfony Community",
+                    "homepage": "https://symfony.com/contributors"
+                }
+            ],
+            "description": "Symfony polyfill backporting some PHP 5.6+ features to lower PHP versions",
+            "homepage": "https://symfony.com",
+            "keywords": [
+                "compatibility",
+                "polyfill",
+                "portable",
+                "shim"
+            ],
+            "time": "2016-05-18 14:26:46"
+        },
+        {
+            "name": "symfony/polyfill-util",
+            "version": "v1.2.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/symfony/polyfill-util.git",
+                "reference": "ef830ce3d218e622b221d6bfad42c751d974bf99"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/symfony/polyfill-util/zipball/ef830ce3d218e622b221d6bfad42c751d974bf99",
+                "reference": "ef830ce3d218e622b221d6bfad42c751d974bf99",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.3.3"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.2-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Symfony\\Polyfill\\Util\\": ""
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Nicolas Grekas",
+                    "email": "p@tchwork.com"
+                },
+                {
+                    "name": "Symfony Community",
+                    "homepage": "https://symfony.com/contributors"
+                }
+            ],
+            "description": "Symfony utilities for portability of PHP codes",
+            "homepage": "https://symfony.com",
+            "keywords": [
+                "compat",
+                "compatibility",
+                "polyfill",
+                "shim"
+            ],
+            "time": "2016-05-18 14:26:46"
+        },
+        {
+            "name": "symfony/process",
+            "version": "v3.0.6",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/symfony/process.git",
+                "reference": "53f9407c0bb1c5a79127db8f7bfe12f0f6f3dcdb"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/symfony/process/zipball/53f9407c0bb1c5a79127db8f7bfe12f0f6f3dcdb",
+                "reference": "53f9407c0bb1c5a79127db8f7bfe12f0f6f3dcdb",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.5.9"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "3.0-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Symfony\\Component\\Process\\": ""
+                },
+                "exclude-from-classmap": [
+                    "/Tests/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Fabien Potencier",
+                    "email": "fabien@symfony.com"
+                },
+                {
+                    "name": "Symfony Community",
+                    "homepage": "https://symfony.com/contributors"
+                }
+            ],
+            "description": "Symfony Process Component",
+            "homepage": "https://symfony.com",
+            "time": "2016-04-14 15:30:28"
+        },
+        {
+            "name": "symfony/routing",
+            "version": "v3.0.6",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/symfony/routing.git",
+                "reference": "a6cd168310066176599442aa21f5da86c3f8e0b3"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/symfony/routing/zipball/a6cd168310066176599442aa21f5da86c3f8e0b3",
+                "reference": "a6cd168310066176599442aa21f5da86c3f8e0b3",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.5.9"
+            },
+            "conflict": {
+                "symfony/config": "<2.8"
+            },
+            "require-dev": {
+                "doctrine/annotations": "~1.0",
+                "doctrine/common": "~2.2",
+                "psr/log": "~1.0",
+                "symfony/config": "~2.8|~3.0",
+                "symfony/expression-language": "~2.8|~3.0",
+                "symfony/http-foundation": "~2.8|~3.0",
+                "symfony/yaml": "~2.8|~3.0"
+            },
+            "suggest": {
+                "doctrine/annotations": "For using the annotation loader",
+                "symfony/config": "For using the all-in-one router or any loader",
+                "symfony/dependency-injection": "For loading routes from a service",
+                "symfony/expression-language": "For using expression matching",
+                "symfony/http-foundation": "For using a Symfony Request object",
+                "symfony/yaml": "For using the YAML loader"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "3.0-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Symfony\\Component\\Routing\\": ""
+                },
+                "exclude-from-classmap": [
+                    "/Tests/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Fabien Potencier",
+                    "email": "fabien@symfony.com"
+                },
+                {
+                    "name": "Symfony Community",
+                    "homepage": "https://symfony.com/contributors"
+                }
+            ],
+            "description": "Symfony Routing Component",
+            "homepage": "https://symfony.com",
+            "keywords": [
+                "router",
+                "routing",
+                "uri",
+                "url"
+            ],
+            "time": "2016-05-03 12:23:49"
+        },
+        {
+            "name": "symfony/translation",
+            "version": "v3.0.6",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/symfony/translation.git",
+                "reference": "f7a07af51ea067745a521dab1e3152044a2fb1f2"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/symfony/translation/zipball/f7a07af51ea067745a521dab1e3152044a2fb1f2",
+                "reference": "f7a07af51ea067745a521dab1e3152044a2fb1f2",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.5.9",
+                "symfony/polyfill-mbstring": "~1.0"
+            },
+            "conflict": {
+                "symfony/config": "<2.8"
+            },
+            "require-dev": {
+                "psr/log": "~1.0",
+                "symfony/config": "~2.8|~3.0",
+                "symfony/intl": "~2.8|~3.0",
+                "symfony/yaml": "~2.8|~3.0"
+            },
+            "suggest": {
+                "psr/log": "To use logging capability in translator",
+                "symfony/config": "",
+                "symfony/yaml": ""
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "3.0-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Symfony\\Component\\Translation\\": ""
+                },
+                "exclude-from-classmap": [
+                    "/Tests/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Fabien Potencier",
+                    "email": "fabien@symfony.com"
+                },
+                {
+                    "name": "Symfony Community",
+                    "homepage": "https://symfony.com/contributors"
+                }
+            ],
+            "description": "Symfony Translation Component",
+            "homepage": "https://symfony.com",
+            "time": "2016-03-25 01:41:20"
+        },
+        {
+            "name": "symfony/var-dumper",
+            "version": "v3.0.6",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/symfony/var-dumper.git",
+                "reference": "0e918c269093ba4c77fca14e9424fa74ed16f1a6"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/symfony/var-dumper/zipball/0e918c269093ba4c77fca14e9424fa74ed16f1a6",
+                "reference": "0e918c269093ba4c77fca14e9424fa74ed16f1a6",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.5.9",
+                "symfony/polyfill-mbstring": "~1.0"
+            },
+            "require-dev": {
+                "twig/twig": "~1.20|~2.0"
+            },
+            "suggest": {
+                "ext-symfony_debug": ""
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "3.0-dev"
+                }
+            },
+            "autoload": {
+                "files": [
+                    "Resources/functions/dump.php"
+                ],
+                "psr-4": {
+                    "Symfony\\Component\\VarDumper\\": ""
+                },
+                "exclude-from-classmap": [
+                    "/Tests/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Nicolas Grekas",
+                    "email": "p@tchwork.com"
+                },
+                {
+                    "name": "Symfony Community",
+                    "homepage": "https://symfony.com/contributors"
+                }
+            ],
+            "description": "Symfony mechanism for exploring and dumping PHP variables",
+            "homepage": "https://symfony.com",
+            "keywords": [
+                "debug",
+                "dump"
+            ],
+            "time": "2016-04-25 11:17:47"
+        },
+        {
+            "name": "themattharris/tmhoauth",
+            "version": "0.8.4",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/themattharris/tmhOAuth.git",
+                "reference": "455552d6c57549632644b6c9ac9204766be2b5ee"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/themattharris/tmhOAuth/zipball/455552d6c57549632644b6c9ac9204766be2b5ee",
+                "reference": "455552d6c57549632644b6c9ac9204766be2b5ee",
+                "shasum": ""
+            },
+            "require": {
+                "ext-curl": "*",
+                "php": ">=5.3.0"
+            },
+            "type": "library",
+            "autoload": {
+                "psr-0": {
+                    "tmhOAuth": ""
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "Apache-2.0"
+            ],
+            "authors": [
+                {
+                    "name": "themattharris",
+                    "email": "matt@themattharris.com",
+                    "role": "Developer"
+                }
+            ],
+            "description": "An OAuth library written in PHP by @themattharris",
+            "keywords": [
+                "oauth",
+                "twitter"
+            ],
+            "time": "2014-08-06 22:29:35"
+        },
+        {
+            "name": "thujohn/twitter",
+            "version": "2.2.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/thujohn/twitter.git",
+                "reference": "137dfa006ad06b956d579ff3ce0ffa5d03506188"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/thujohn/twitter/zipball/137dfa006ad06b956d579ff3ce0ffa5d03506188",
+                "reference": "137dfa006ad06b956d579ff3ce0ffa5d03506188",
+                "shasum": ""
+            },
+            "require": {
+                "illuminate/support": "4.*|5.*",
+                "php": ">=5.4.0",
+                "themattharris/tmhoauth": "0.8.4"
+            },
+            "type": "library",
+            "autoload": {
+                "psr-0": {
+                    "Thujohn\\Twitter": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "thujohn",
+                    "email": "jonathan.thuau@gmail.com"
+                }
+            ],
+            "description": "Twitter API for Laravel",
+            "keywords": [
+                "laravel",
+                "laravel4",
+                "laravel5",
+                "twitter"
+            ],
+            "time": "2016-04-08 11:43:15"
+        },
+        {
+            "name": "vlucas/phpdotenv",
+            "version": "v2.2.1",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/vlucas/phpdotenv.git",
+                "reference": "63f37b9395e8041cd4313129c08ece896d06ca8e"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/vlucas/phpdotenv/zipball/63f37b9395e8041cd4313129c08ece896d06ca8e",
+                "reference": "63f37b9395e8041cd4313129c08ece896d06ca8e",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.3.9"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "^4.8 || ^5.0"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "2.2-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Dotenv\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-3-Clause-Attribution"
+            ],
+            "authors": [
+                {
+                    "name": "Vance Lucas",
+                    "email": "vance@vancelucas.com",
+                    "homepage": "http://www.vancelucas.com"
+                }
+            ],
+            "description": "Loads environment variables from `.env` to `getenv()`, `$_ENV` and `$_SERVER` automagically.",
+            "keywords": [
+                "dotenv",
+                "env",
+                "environment"
+            ],
+            "time": "2016-04-15 10:48:49"
+        }
+    ],
+    "packages-dev": [
+        {
+            "name": "barryvdh/laravel-debugbar",
+            "version": "v2.2.2",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/barryvdh/laravel-debugbar.git",
+                "reference": "c291e58d0a13953e0f68d99182ee77ebc693edc0"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/barryvdh/laravel-debugbar/zipball/c291e58d0a13953e0f68d99182ee77ebc693edc0",
+                "reference": "c291e58d0a13953e0f68d99182ee77ebc693edc0",
+                "shasum": ""
+            },
+            "require": {
+                "illuminate/support": "5.1.*|5.2.*",
+                "maximebf/debugbar": "~1.11.0",
+                "php": ">=5.5.9",
+                "symfony/finder": "~2.7|~3.0"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "2.2-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Barryvdh\\Debugbar\\": "src/"
+                },
+                "files": [
+                    "src/helpers.php"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Barry vd. Heuvel",
+                    "email": "barryvdh@gmail.com"
+                }
+            ],
+            "description": "PHP Debugbar integration for Laravel",
+            "keywords": [
+                "debug",
+                "debugbar",
+                "laravel",
+                "profiler",
+                "webprofiler"
+            ],
+            "time": "2016-05-11 13:54:43"
+        },
+        {
+            "name": "doctrine/instantiator",
+            "version": "1.0.5",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/doctrine/instantiator.git",
+                "reference": "8e884e78f9f0eb1329e445619e04456e64d8051d"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/doctrine/instantiator/zipball/8e884e78f9f0eb1329e445619e04456e64d8051d",
+                "reference": "8e884e78f9f0eb1329e445619e04456e64d8051d",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.3,<8.0-DEV"
+            },
+            "require-dev": {
+                "athletic/athletic": "~0.1.8",
+                "ext-pdo": "*",
+                "ext-phar": "*",
+                "phpunit/phpunit": "~4.0",
+                "squizlabs/php_codesniffer": "~2.0"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.0.x-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Doctrine\\Instantiator\\": "src/Doctrine/Instantiator/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Marco Pivetta",
+                    "email": "ocramius@gmail.com",
+                    "homepage": "http://ocramius.github.com/"
+                }
+            ],
+            "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors",
+            "homepage": "https://github.com/doctrine/instantiator",
+            "keywords": [
+                "constructor",
+                "instantiate"
+            ],
+            "time": "2015-06-14 21:17:01"
+        },
+        {
+            "name": "filp/whoops",
+            "version": "2.1.2",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/filp/whoops.git",
+                "reference": "d13505b240a6f580bc75ba591da30299d6cb0eec"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/filp/whoops/zipball/d13505b240a6f580bc75ba591da30299d6cb0eec",
+                "reference": "d13505b240a6f580bc75ba591da30299d6cb0eec",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.5.9"
+            },
+            "require-dev": {
+                "mockery/mockery": "0.9.*",
+                "phpunit/phpunit": "^4.8 || ^5.0",
+                "symfony/var-dumper": "~3.0"
+            },
+            "suggest": {
+                "symfony/var-dumper": "Pretty print complex values better with var-dumper available",
+                "whoops/soap": "Formats errors as SOAP responses"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "2.0-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Whoops\\": "src/Whoops/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Filipe Dobreira",
+                    "homepage": "https://github.com/filp",
+                    "role": "Developer"
+                }
+            ],
+            "description": "php error handling for cool kids",
+            "homepage": "https://github.com/filp/whoops",
+            "keywords": [
+                "error",
+                "exception",
+                "handling",
+                "library",
+                "whoops",
+                "zf2"
+            ],
+            "time": "2016-04-07 06:16:25"
+        },
+        {
+            "name": "fzaninotto/faker",
+            "version": "v1.6.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/fzaninotto/Faker.git",
+                "reference": "44f9a286a04b80c76a4e5fb7aad8bb539b920123"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/fzaninotto/Faker/zipball/44f9a286a04b80c76a4e5fb7aad8bb539b920123",
+                "reference": "44f9a286a04b80c76a4e5fb7aad8bb539b920123",
+                "shasum": ""
+            },
+            "require": {
+                "php": "^5.3.3|^7.0"
+            },
+            "require-dev": {
+                "ext-intl": "*",
+                "phpunit/phpunit": "~4.0",
+                "squizlabs/php_codesniffer": "~1.5"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": []
+            },
+            "autoload": {
+                "psr-4": {
+                    "Faker\\": "src/Faker/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "François Zaninotto"
+                }
+            ],
+            "description": "Faker is a PHP library that generates fake data for you.",
+            "keywords": [
+                "data",
+                "faker",
+                "fixtures"
+            ],
+            "time": "2016-04-29 12:21:54"
+        },
+        {
+            "name": "hamcrest/hamcrest-php",
+            "version": "v1.2.2",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/hamcrest/hamcrest-php.git",
+                "reference": "b37020aa976fa52d3de9aa904aa2522dc518f79c"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/hamcrest/hamcrest-php/zipball/b37020aa976fa52d3de9aa904aa2522dc518f79c",
+                "reference": "b37020aa976fa52d3de9aa904aa2522dc518f79c",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.3.2"
+            },
+            "replace": {
+                "cordoval/hamcrest-php": "*",
+                "davedevelopment/hamcrest-php": "*",
+                "kodova/hamcrest-php": "*"
+            },
+            "require-dev": {
+                "phpunit/php-file-iterator": "1.3.3",
+                "satooshi/php-coveralls": "dev-master"
+            },
+            "type": "library",
+            "autoload": {
+                "classmap": [
+                    "hamcrest"
+                ],
+                "files": [
+                    "hamcrest/Hamcrest.php"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD"
+            ],
+            "description": "This is the PHP port of Hamcrest Matchers",
+            "keywords": [
+                "test"
+            ],
+            "time": "2015-05-11 14:41:42"
+        },
+        {
+            "name": "maximebf/debugbar",
+            "version": "v1.11.1",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/maximebf/php-debugbar.git",
+                "reference": "d9302891c1f0a0ac5a4f66725163a00537c6359f"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/maximebf/php-debugbar/zipball/d9302891c1f0a0ac5a4f66725163a00537c6359f",
+                "reference": "d9302891c1f0a0ac5a4f66725163a00537c6359f",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.3.0",
+                "psr/log": "^1.0",
+                "symfony/var-dumper": "^2.6|^3.0"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "^4.0|^5.0"
+            },
+            "suggest": {
+                "kriswallsmith/assetic": "The best way to manage assets",
+                "monolog/monolog": "Log using Monolog",
+                "predis/predis": "Redis storage"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.11-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "DebugBar\\": "src/DebugBar/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Maxime Bouroumeau-Fuseau",
+                    "email": "maxime.bouroumeau@gmail.com",
+                    "homepage": "http://maximebf.com"
+                },
+                {
+                    "name": "Barry vd. Heuvel",
+                    "email": "barryvdh@gmail.com"
+                }
+            ],
+            "description": "Debug bar in the browser for php application",
+            "homepage": "https://github.com/maximebf/php-debugbar",
+            "keywords": [
+                "debug",
+                "debugbar"
+            ],
+            "time": "2016-01-22 12:22:23"
+        },
+        {
+            "name": "mockery/mockery",
+            "version": "0.9.5",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/padraic/mockery.git",
+                "reference": "4db079511a283e5aba1b3c2fb19037c645e70fc2"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/padraic/mockery/zipball/4db079511a283e5aba1b3c2fb19037c645e70fc2",
+                "reference": "4db079511a283e5aba1b3c2fb19037c645e70fc2",
+                "shasum": ""
+            },
+            "require": {
+                "hamcrest/hamcrest-php": "~1.1",
+                "lib-pcre": ">=7.0",
+                "php": ">=5.3.2"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "~4.0"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "0.9.x-dev"
+                }
+            },
+            "autoload": {
+                "psr-0": {
+                    "Mockery": "library/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-3-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Pádraic Brady",
+                    "email": "padraic.brady@gmail.com",
+                    "homepage": "http://blog.astrumfutura.com"
+                },
+                {
+                    "name": "Dave Marshall",
+                    "email": "dave.marshall@atstsolutions.co.uk",
+                    "homepage": "http://davedevelopment.co.uk"
+                }
+            ],
+            "description": "Mockery is a simple yet flexible PHP mock object framework for use in unit testing with PHPUnit, PHPSpec or any other testing framework. Its core goal is to offer a test double framework with a succinct API capable of clearly defining all possible object operations and interactions using a human readable Domain Specific Language (DSL). Designed as a drop in alternative to PHPUnit's phpunit-mock-objects library, Mockery is easy to integrate with PHPUnit and can operate alongside phpunit-mock-objects without the World ending.",
+            "homepage": "http://github.com/padraic/mockery",
+            "keywords": [
+                "BDD",
+                "TDD",
+                "library",
+                "mock",
+                "mock objects",
+                "mockery",
+                "stub",
+                "test",
+                "test double",
+                "testing"
+            ],
+            "time": "2016-05-22 21:52:33"
+        },
+        {
+            "name": "phpdocumentor/reflection-docblock",
+            "version": "2.0.4",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git",
+                "reference": "d68dbdc53dc358a816f00b300704702b2eaff7b8"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/d68dbdc53dc358a816f00b300704702b2eaff7b8",
+                "reference": "d68dbdc53dc358a816f00b300704702b2eaff7b8",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.3.3"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "~4.0"
+            },
+            "suggest": {
+                "dflydev/markdown": "~1.0",
+                "erusev/parsedown": "~1.0"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "2.0.x-dev"
+                }
+            },
+            "autoload": {
+                "psr-0": {
+                    "phpDocumentor": [
+                        "src/"
+                    ]
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Mike van Riel",
+                    "email": "mike.vanriel@naenius.com"
+                }
+            ],
+            "time": "2015-02-03 12:10:50"
+        },
+        {
+            "name": "phpspec/prophecy",
+            "version": "v1.6.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/phpspec/prophecy.git",
+                "reference": "3c91bdf81797d725b14cb62906f9a4ce44235972"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/phpspec/prophecy/zipball/3c91bdf81797d725b14cb62906f9a4ce44235972",
+                "reference": "3c91bdf81797d725b14cb62906f9a4ce44235972",
+                "shasum": ""
+            },
+            "require": {
+                "doctrine/instantiator": "^1.0.2",
+                "php": "^5.3|^7.0",
+                "phpdocumentor/reflection-docblock": "~2.0",
+                "sebastian/comparator": "~1.1",
+                "sebastian/recursion-context": "~1.0"
+            },
+            "require-dev": {
+                "phpspec/phpspec": "~2.0"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.5.x-dev"
+                }
+            },
+            "autoload": {
+                "psr-0": {
+                    "Prophecy\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Konstantin Kudryashov",
+                    "email": "ever.zet@gmail.com",
+                    "homepage": "http://everzet.com"
+                },
+                {
+                    "name": "Marcello Duarte",
+                    "email": "marcello.duarte@gmail.com"
+                }
+            ],
+            "description": "Highly opinionated mocking framework for PHP 5.3+",
+            "homepage": "https://github.com/phpspec/prophecy",
+            "keywords": [
+                "Double",
+                "Dummy",
+                "fake",
+                "mock",
+                "spy",
+                "stub"
+            ],
+            "time": "2016-02-15 07:46:21"
+        },
+        {
+            "name": "phpunit/php-code-coverage",
+            "version": "2.2.4",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/sebastianbergmann/php-code-coverage.git",
+                "reference": "eabf68b476ac7d0f73793aada060f1c1a9bf8979"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/eabf68b476ac7d0f73793aada060f1c1a9bf8979",
+                "reference": "eabf68b476ac7d0f73793aada060f1c1a9bf8979",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.3.3",
+                "phpunit/php-file-iterator": "~1.3",
+                "phpunit/php-text-template": "~1.2",
+                "phpunit/php-token-stream": "~1.3",
+                "sebastian/environment": "^1.3.2",
+                "sebastian/version": "~1.0"
+            },
+            "require-dev": {
+                "ext-xdebug": ">=2.1.4",
+                "phpunit/phpunit": "~4"
+            },
+            "suggest": {
+                "ext-dom": "*",
+                "ext-xdebug": ">=2.2.1",
+                "ext-xmlwriter": "*"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "2.2.x-dev"
+                }
+            },
+            "autoload": {
+                "classmap": [
+                    "src/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-3-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Sebastian Bergmann",
+                    "email": "sb@sebastian-bergmann.de",
+                    "role": "lead"
+                }
+            ],
+            "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.",
+            "homepage": "https://github.com/sebastianbergmann/php-code-coverage",
+            "keywords": [
+                "coverage",
+                "testing",
+                "xunit"
+            ],
+            "time": "2015-10-06 15:47:00"
+        },
+        {
+            "name": "phpunit/php-file-iterator",
+            "version": "1.4.1",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/sebastianbergmann/php-file-iterator.git",
+                "reference": "6150bf2c35d3fc379e50c7602b75caceaa39dbf0"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/6150bf2c35d3fc379e50c7602b75caceaa39dbf0",
+                "reference": "6150bf2c35d3fc379e50c7602b75caceaa39dbf0",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.3.3"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.4.x-dev"
+                }
+            },
+            "autoload": {
+                "classmap": [
+                    "src/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-3-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Sebastian Bergmann",
+                    "email": "sb@sebastian-bergmann.de",
+                    "role": "lead"
+                }
+            ],
+            "description": "FilterIterator implementation that filters files based on a list of suffixes.",
+            "homepage": "https://github.com/sebastianbergmann/php-file-iterator/",
+            "keywords": [
+                "filesystem",
+                "iterator"
+            ],
+            "time": "2015-06-21 13:08:43"
+        },
+        {
+            "name": "phpunit/php-text-template",
+            "version": "1.2.1",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/sebastianbergmann/php-text-template.git",
+                "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/31f8b717e51d9a2afca6c9f046f5d69fc27c8686",
+                "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.3.3"
+            },
+            "type": "library",
+            "autoload": {
+                "classmap": [
+                    "src/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-3-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Sebastian Bergmann",
+                    "email": "sebastian@phpunit.de",
+                    "role": "lead"
+                }
+            ],
+            "description": "Simple template engine.",
+            "homepage": "https://github.com/sebastianbergmann/php-text-template/",
+            "keywords": [
+                "template"
+            ],
+            "time": "2015-06-21 13:50:34"
+        },
+        {
+            "name": "phpunit/php-timer",
+            "version": "1.0.8",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/sebastianbergmann/php-timer.git",
+                "reference": "38e9124049cf1a164f1e4537caf19c99bf1eb260"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/38e9124049cf1a164f1e4537caf19c99bf1eb260",
+                "reference": "38e9124049cf1a164f1e4537caf19c99bf1eb260",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.3.3"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "~4|~5"
+            },
+            "type": "library",
+            "autoload": {
+                "classmap": [
+                    "src/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-3-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Sebastian Bergmann",
+                    "email": "sb@sebastian-bergmann.de",
+                    "role": "lead"
+                }
+            ],
+            "description": "Utility class for timing",
+            "homepage": "https://github.com/sebastianbergmann/php-timer/",
+            "keywords": [
+                "timer"
+            ],
+            "time": "2016-05-12 18:03:57"
+        },
+        {
+            "name": "phpunit/php-token-stream",
+            "version": "1.4.8",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/sebastianbergmann/php-token-stream.git",
+                "reference": "3144ae21711fb6cac0b1ab4cbe63b75ce3d4e8da"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/3144ae21711fb6cac0b1ab4cbe63b75ce3d4e8da",
+                "reference": "3144ae21711fb6cac0b1ab4cbe63b75ce3d4e8da",
+                "shasum": ""
+            },
+            "require": {
+                "ext-tokenizer": "*",
+                "php": ">=5.3.3"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "~4.2"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.4-dev"
+                }
+            },
+            "autoload": {
+                "classmap": [
+                    "src/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-3-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Sebastian Bergmann",
+                    "email": "sebastian@phpunit.de"
+                }
+            ],
+            "description": "Wrapper around PHP's tokenizer extension.",
+            "homepage": "https://github.com/sebastianbergmann/php-token-stream/",
+            "keywords": [
+                "tokenizer"
+            ],
+            "time": "2015-09-15 10:49:45"
+        },
+        {
+            "name": "phpunit/phpunit",
+            "version": "4.8.26",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/sebastianbergmann/phpunit.git",
+                "reference": "fc1d8cd5b5de11625979125c5639347896ac2c74"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/fc1d8cd5b5de11625979125c5639347896ac2c74",
+                "reference": "fc1d8cd5b5de11625979125c5639347896ac2c74",
+                "shasum": ""
+            },
+            "require": {
+                "ext-dom": "*",
+                "ext-json": "*",
+                "ext-pcre": "*",
+                "ext-reflection": "*",
+                "ext-spl": "*",
+                "php": ">=5.3.3",
+                "phpspec/prophecy": "^1.3.1",
+                "phpunit/php-code-coverage": "~2.1",
+                "phpunit/php-file-iterator": "~1.4",
+                "phpunit/php-text-template": "~1.2",
+                "phpunit/php-timer": "^1.0.6",
+                "phpunit/phpunit-mock-objects": "~2.3",
+                "sebastian/comparator": "~1.1",
+                "sebastian/diff": "~1.2",
+                "sebastian/environment": "~1.3",
+                "sebastian/exporter": "~1.2",
+                "sebastian/global-state": "~1.0",
+                "sebastian/version": "~1.0",
+                "symfony/yaml": "~2.1|~3.0"
+            },
+            "suggest": {
+                "phpunit/php-invoker": "~1.1"
+            },
+            "bin": [
+                "phpunit"
+            ],
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "4.8.x-dev"
+                }
+            },
+            "autoload": {
+                "classmap": [
+                    "src/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-3-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Sebastian Bergmann",
+                    "email": "sebastian@phpunit.de",
+                    "role": "lead"
+                }
+            ],
+            "description": "The PHP Unit Testing framework.",
+            "homepage": "https://phpunit.de/",
+            "keywords": [
+                "phpunit",
+                "testing",
+                "xunit"
+            ],
+            "time": "2016-05-17 03:09:28"
+        },
+        {
+            "name": "phpunit/phpunit-mock-objects",
+            "version": "2.3.8",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/sebastianbergmann/phpunit-mock-objects.git",
+                "reference": "ac8e7a3db35738d56ee9a76e78a4e03d97628983"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/sebastianbergmann/phpunit-mock-objects/zipball/ac8e7a3db35738d56ee9a76e78a4e03d97628983",
+                "reference": "ac8e7a3db35738d56ee9a76e78a4e03d97628983",
+                "shasum": ""
+            },
+            "require": {
+                "doctrine/instantiator": "^1.0.2",
+                "php": ">=5.3.3",
+                "phpunit/php-text-template": "~1.2",
+                "sebastian/exporter": "~1.2"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "~4.4"
+            },
+            "suggest": {
+                "ext-soap": "*"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "2.3.x-dev"
+                }
+            },
+            "autoload": {
+                "classmap": [
+                    "src/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-3-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Sebastian Bergmann",
+                    "email": "sb@sebastian-bergmann.de",
+                    "role": "lead"
+                }
+            ],
+            "description": "Mock Object library for PHPUnit",
+            "homepage": "https://github.com/sebastianbergmann/phpunit-mock-objects/",
+            "keywords": [
+                "mock",
+                "xunit"
+            ],
+            "time": "2015-10-02 06:51:40"
+        },
+        {
+            "name": "sebastian/comparator",
+            "version": "1.2.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/sebastianbergmann/comparator.git",
+                "reference": "937efb279bd37a375bcadf584dec0726f84dbf22"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/937efb279bd37a375bcadf584dec0726f84dbf22",
+                "reference": "937efb279bd37a375bcadf584dec0726f84dbf22",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.3.3",
+                "sebastian/diff": "~1.2",
+                "sebastian/exporter": "~1.2"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "~4.4"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.2.x-dev"
+                }
+            },
+            "autoload": {
+                "classmap": [
+                    "src/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-3-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Jeff Welch",
+                    "email": "whatthejeff@gmail.com"
+                },
+                {
+                    "name": "Volker Dusch",
+                    "email": "github@wallbash.com"
+                },
+                {
+                    "name": "Bernhard Schussek",
+                    "email": "bschussek@2bepublished.at"
+                },
+                {
+                    "name": "Sebastian Bergmann",
+                    "email": "sebastian@phpunit.de"
+                }
+            ],
+            "description": "Provides the functionality to compare PHP values for equality",
+            "homepage": "http://www.github.com/sebastianbergmann/comparator",
+            "keywords": [
+                "comparator",
+                "compare",
+                "equality"
+            ],
+            "time": "2015-07-26 15:48:44"
+        },
+        {
+            "name": "sebastian/diff",
+            "version": "1.4.1",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/sebastianbergmann/diff.git",
+                "reference": "13edfd8706462032c2f52b4b862974dd46b71c9e"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/13edfd8706462032c2f52b4b862974dd46b71c9e",
+                "reference": "13edfd8706462032c2f52b4b862974dd46b71c9e",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.3.3"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "~4.8"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.4-dev"
+                }
+            },
+            "autoload": {
+                "classmap": [
+                    "src/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-3-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Kore Nordmann",
+                    "email": "mail@kore-nordmann.de"
+                },
+                {
+                    "name": "Sebastian Bergmann",
+                    "email": "sebastian@phpunit.de"
+                }
+            ],
+            "description": "Diff implementation",
+            "homepage": "https://github.com/sebastianbergmann/diff",
+            "keywords": [
+                "diff"
+            ],
+            "time": "2015-12-08 07:14:41"
+        },
+        {
+            "name": "sebastian/environment",
+            "version": "1.3.7",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/sebastianbergmann/environment.git",
+                "reference": "4e8f0da10ac5802913afc151413bc8c53b6c2716"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/4e8f0da10ac5802913afc151413bc8c53b6c2716",
+                "reference": "4e8f0da10ac5802913afc151413bc8c53b6c2716",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.3.3"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "~4.4"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.3.x-dev"
+                }
+            },
+            "autoload": {
+                "classmap": [
+                    "src/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-3-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Sebastian Bergmann",
+                    "email": "sebastian@phpunit.de"
+                }
+            ],
+            "description": "Provides functionality to handle HHVM/PHP environments",
+            "homepage": "http://www.github.com/sebastianbergmann/environment",
+            "keywords": [
+                "Xdebug",
+                "environment",
+                "hhvm"
+            ],
+            "time": "2016-05-17 03:18:57"
+        },
+        {
+            "name": "sebastian/exporter",
+            "version": "1.2.1",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/sebastianbergmann/exporter.git",
+                "reference": "7ae5513327cb536431847bcc0c10edba2701064e"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/7ae5513327cb536431847bcc0c10edba2701064e",
+                "reference": "7ae5513327cb536431847bcc0c10edba2701064e",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.3.3",
+                "sebastian/recursion-context": "~1.0"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "~4.4"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.2.x-dev"
+                }
+            },
+            "autoload": {
+                "classmap": [
+                    "src/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-3-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Jeff Welch",
+                    "email": "whatthejeff@gmail.com"
+                },
+                {
+                    "name": "Volker Dusch",
+                    "email": "github@wallbash.com"
+                },
+                {
+                    "name": "Bernhard Schussek",
+                    "email": "bschussek@2bepublished.at"
+                },
+                {
+                    "name": "Sebastian Bergmann",
+                    "email": "sebastian@phpunit.de"
+                },
+                {
+                    "name": "Adam Harvey",
+                    "email": "aharvey@php.net"
+                }
+            ],
+            "description": "Provides the functionality to export PHP variables for visualization",
+            "homepage": "http://www.github.com/sebastianbergmann/exporter",
+            "keywords": [
+                "export",
+                "exporter"
+            ],
+            "time": "2015-06-21 07:55:53"
+        },
+        {
+            "name": "sebastian/global-state",
+            "version": "1.1.1",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/sebastianbergmann/global-state.git",
+                "reference": "bc37d50fea7d017d3d340f230811c9f1d7280af4"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/bc37d50fea7d017d3d340f230811c9f1d7280af4",
+                "reference": "bc37d50fea7d017d3d340f230811c9f1d7280af4",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.3.3"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "~4.2"
+            },
+            "suggest": {
+                "ext-uopz": "*"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.0-dev"
+                }
+            },
+            "autoload": {
+                "classmap": [
+                    "src/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-3-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Sebastian Bergmann",
+                    "email": "sebastian@phpunit.de"
+                }
+            ],
+            "description": "Snapshotting of global state",
+            "homepage": "http://www.github.com/sebastianbergmann/global-state",
+            "keywords": [
+                "global state"
+            ],
+            "time": "2015-10-12 03:26:01"
+        },
+        {
+            "name": "sebastian/recursion-context",
+            "version": "1.0.2",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/sebastianbergmann/recursion-context.git",
+                "reference": "913401df809e99e4f47b27cdd781f4a258d58791"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/913401df809e99e4f47b27cdd781f4a258d58791",
+                "reference": "913401df809e99e4f47b27cdd781f4a258d58791",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.3.3"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "~4.4"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.0.x-dev"
+                }
+            },
+            "autoload": {
+                "classmap": [
+                    "src/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-3-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Jeff Welch",
+                    "email": "whatthejeff@gmail.com"
+                },
+                {
+                    "name": "Sebastian Bergmann",
+                    "email": "sebastian@phpunit.de"
+                },
+                {
+                    "name": "Adam Harvey",
+                    "email": "aharvey@php.net"
+                }
+            ],
+            "description": "Provides functionality to recursively process PHP variables",
+            "homepage": "http://www.github.com/sebastianbergmann/recursion-context",
+            "time": "2015-11-11 19:50:13"
+        },
+        {
+            "name": "sebastian/version",
+            "version": "1.0.6",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/sebastianbergmann/version.git",
+                "reference": "58b3a85e7999757d6ad81c787a1fbf5ff6c628c6"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/58b3a85e7999757d6ad81c787a1fbf5ff6c628c6",
+                "reference": "58b3a85e7999757d6ad81c787a1fbf5ff6c628c6",
+                "shasum": ""
+            },
+            "type": "library",
+            "autoload": {
+                "classmap": [
+                    "src/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-3-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Sebastian Bergmann",
+                    "email": "sebastian@phpunit.de",
+                    "role": "lead"
+                }
+            ],
+            "description": "Library that helps with managing the version number of Git-hosted PHP projects",
+            "homepage": "https://github.com/sebastianbergmann/version",
+            "time": "2015-06-21 13:59:46"
+        },
+        {
+            "name": "symfony/css-selector",
+            "version": "v3.0.6",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/symfony/css-selector.git",
+                "reference": "65e764f404685f2dc20c057e889b3ad04b2e2db0"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/symfony/css-selector/zipball/65e764f404685f2dc20c057e889b3ad04b2e2db0",
+                "reference": "65e764f404685f2dc20c057e889b3ad04b2e2db0",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.5.9"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "3.0-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Symfony\\Component\\CssSelector\\": ""
+                },
+                "exclude-from-classmap": [
+                    "/Tests/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Jean-François Simon",
+                    "email": "jeanfrancois.simon@sensiolabs.com"
+                },
+                {
+                    "name": "Fabien Potencier",
+                    "email": "fabien@symfony.com"
+                },
+                {
+                    "name": "Symfony Community",
+                    "homepage": "https://symfony.com/contributors"
+                }
+            ],
+            "description": "Symfony CssSelector Component",
+            "homepage": "https://symfony.com",
+            "time": "2016-03-04 07:55:57"
+        },
+        {
+            "name": "symfony/dom-crawler",
+            "version": "v3.0.6",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/symfony/dom-crawler.git",
+                "reference": "49b588841225b205700e5122fa01911cabada857"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/symfony/dom-crawler/zipball/49b588841225b205700e5122fa01911cabada857",
+                "reference": "49b588841225b205700e5122fa01911cabada857",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.5.9",
+                "symfony/polyfill-mbstring": "~1.0"
+            },
+            "require-dev": {
+                "symfony/css-selector": "~2.8|~3.0"
+            },
+            "suggest": {
+                "symfony/css-selector": ""
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "3.0-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Symfony\\Component\\DomCrawler\\": ""
+                },
+                "exclude-from-classmap": [
+                    "/Tests/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Fabien Potencier",
+                    "email": "fabien@symfony.com"
+                },
+                {
+                    "name": "Symfony Community",
+                    "homepage": "https://symfony.com/contributors"
+                }
+            ],
+            "description": "Symfony DomCrawler Component",
+            "homepage": "https://symfony.com",
+            "time": "2016-04-12 18:09:53"
+        },
+        {
+            "name": "symfony/yaml",
+            "version": "v3.0.6",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/symfony/yaml.git",
+                "reference": "0047c8366744a16de7516622c5b7355336afae96"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/symfony/yaml/zipball/0047c8366744a16de7516622c5b7355336afae96",
+                "reference": "0047c8366744a16de7516622c5b7355336afae96",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.5.9"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "3.0-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Symfony\\Component\\Yaml\\": ""
+                },
+                "exclude-from-classmap": [
+                    "/Tests/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Fabien Potencier",
+                    "email": "fabien@symfony.com"
+                },
+                {
+                    "name": "Symfony Community",
+                    "homepage": "https://symfony.com/contributors"
+                }
+            ],
+            "description": "Symfony Yaml Component",
+            "homepage": "https://symfony.com",
+            "time": "2016-03-04 07:55:57"
+        }
+    ],
+    "aliases": [],
+    "minimum-stability": "stable",
+    "stability-flags": {
+        "jonnybarnes/unicode-tools": 20,
+        "jonnybarnes/indieweb": 20,
+        "jonnybarnes/webmentions-parser": 20,
+        "phaza/laravel-postgis": 20
+    },
+    "prefer-stable": false,
+    "prefer-lowest": false,
+    "platform": {
+        "ext-intl": "*",
+        "php": ">=7.0.0"
+    },
+    "platform-dev": []
+}
diff --git a/config/app.php b/config/app.php
new file mode 100644
index 00000000..1bcc5c20
--- /dev/null
+++ b/config/app.php
@@ -0,0 +1,241 @@
+ env('APP_ENV', 'production'),
+
+    /*
+    |--------------------------------------------------------------------------
+    | Application Debug Mode
+    |--------------------------------------------------------------------------
+    |
+    | When your application is in debug mode, detailed error messages with
+    | stack traces will be shown on every error that occurs within your
+    | application. If disabled, a simple generic error page is shown.
+    |
+    */
+
+    'debug' => env('APP_DEBUG', false),
+
+    /*
+    |--------------------------------------------------------------------------
+    | Application URL
+    |--------------------------------------------------------------------------
+    |
+    | This URL is used by the console to properly generate URLs when using
+    | the Artisan command line tool. You should set this to the root of
+    | your application so that it is used when running Artisan tasks.
+    |
+    */
+
+    'url' => env('APP_URL', 'http://localhost'),
+
+    /*
+    |--------------------------------------------------------------------------
+    | Application Short URL
+    |--------------------------------------------------------------------------
+    |
+    | The short URL for the application
+    |
+    */
+
+    'shorturl' => env('APP_SHORTURL', 'http://shorturl.local'),
+
+    /*
+    |--------------------------------------------------------------------------
+    | Application Timezone
+    |--------------------------------------------------------------------------
+    |
+    | Here you may specify the default timezone for your application, which
+    | will be used by the PHP date and date-time functions. We have gone
+    | ahead and set this to a sensible default for you out of the box.
+    |
+    */
+
+    'timezone' => env('APP_TIMEZONE', 'UTC'),
+
+    /*
+    |--------------------------------------------------------------------------
+    | Application Locale Configuration
+    |--------------------------------------------------------------------------
+    |
+    | The application locale determines the default locale that will be used
+    | by the translation service provider. You are free to set this value
+    | to any of the locales which will be supported by the application.
+    |
+    */
+
+    'locale' => env('APP_LANG', 'en'),
+
+    /*
+    |--------------------------------------------------------------------------
+    | Application Fallback Locale
+    |--------------------------------------------------------------------------
+    |
+    | The fallback locale determines the locale to use when the current one
+    | is not available. You may change the value to correspond to any of
+    | the language folders that are provided through your application.
+    |
+    */
+
+    'fallback_locale' => 'en',
+
+    /*
+    |--------------------------------------------------------------------------
+    | Encryption Key
+    |--------------------------------------------------------------------------
+    |
+    | This key is used by the Illuminate encrypter service and should be set
+    | to a random, 32 character string, otherwise these encrypted strings
+    | will not be safe. Please do this before deploying an application!
+    |
+    */
+
+    'key' => env('APP_KEY'),
+
+    'cipher' => 'AES-256-CBC',
+
+    /*
+    |--------------------------------------------------------------------------
+    | Logging Configuration
+    |--------------------------------------------------------------------------
+    |
+    | Here you may configure the log settings for your application. Out of
+    | the box, Laravel uses the Monolog PHP logging library. This gives
+    | you a variety of powerful log handlers / formatters to utilize.
+    |
+    | Available Settings: "single", "daily", "syslog", "errorlog"
+    |
+    */
+
+    'log' => env('APP_LOG', 'single'),
+
+    /*
+    |--------------------------------------------------------------------------
+    | Autoloaded Service Providers
+    |--------------------------------------------------------------------------
+    |
+    | The service providers listed here will be automatically loaded on the
+    | request to your application. Feel free to add your own services to
+    | this array to grant expanded functionality to your applications.
+    |
+    */
+
+    'providers' => [
+
+        /*
+         * Laravel Framework Service Providers...
+         */
+        Illuminate\Auth\AuthServiceProvider::class,
+        Illuminate\Broadcasting\BroadcastServiceProvider::class,
+        Illuminate\Bus\BusServiceProvider::class,
+        Illuminate\Cache\CacheServiceProvider::class,
+        Illuminate\Foundation\Providers\ConsoleSupportServiceProvider::class,
+        Illuminate\Cookie\CookieServiceProvider::class,
+        Illuminate\Database\DatabaseServiceProvider::class,
+        Illuminate\Encryption\EncryptionServiceProvider::class,
+        Illuminate\Filesystem\FilesystemServiceProvider::class,
+        Illuminate\Foundation\Providers\FoundationServiceProvider::class,
+        Illuminate\Hashing\HashServiceProvider::class,
+        Illuminate\Mail\MailServiceProvider::class,
+        Illuminate\Pagination\PaginationServiceProvider::class,
+        Illuminate\Pipeline\PipelineServiceProvider::class,
+        Illuminate\Queue\QueueServiceProvider::class,
+        Illuminate\Redis\RedisServiceProvider::class,
+        Illuminate\Auth\Passwords\PasswordResetServiceProvider::class,
+        Illuminate\Session\SessionServiceProvider::class,
+        Illuminate\Translation\TranslationServiceProvider::class,
+        Illuminate\Validation\ValidationServiceProvider::class,
+        Illuminate\View\ViewServiceProvider::class,
+
+        /*
+         * Application Service Providers...
+         */
+        App\Providers\AppServiceProvider::class,
+        App\Providers\AuthServiceProvider::class,
+        App\Providers\EventServiceProvider::class,
+        App\Providers\RouteServiceProvider::class,
+
+        /*
+         * Laravel Debugbar
+         */
+        Barryvdh\Debugbar\ServiceProvider::class,
+
+        /*
+         * Thujohn’s Twitter API client
+         */
+        Thujohn\Twitter\TwitterServiceProvider::class,
+
+        /*
+         * Laravel Medialibrary
+         */
+        Spatie\MediaLibrary\MediaLibraryServiceProvider::class,
+
+        /*
+         * Phaza’s Postgis library
+         */
+        Phaza\LaravelPostgis\DatabaseServiceProvider::class,
+
+    ],
+
+    /*
+    |--------------------------------------------------------------------------
+    | Class Aliases
+    |--------------------------------------------------------------------------
+    |
+    | This array of class aliases will be registered when this application
+    | is started. However, feel free to register as many as you wish as
+    | the aliases are "lazy" loaded so they don't hinder performance.
+    |
+    */
+
+    'aliases' => [
+
+        'App' => Illuminate\Support\Facades\App::class,
+        'Artisan' => Illuminate\Support\Facades\Artisan::class,
+        'Auth' => Illuminate\Support\Facades\Auth::class,
+        'Blade' => Illuminate\Support\Facades\Blade::class,
+        'Cache' => Illuminate\Support\Facades\Cache::class,
+        'Config' => Illuminate\Support\Facades\Config::class,
+        'Cookie' => Illuminate\Support\Facades\Cookie::class,
+        'Crypt' => Illuminate\Support\Facades\Crypt::class,
+        'DB' => Illuminate\Support\Facades\DB::class,
+        'Eloquent' => Illuminate\Database\Eloquent\Model::class,
+        'Event' => Illuminate\Support\Facades\Event::class,
+        'File' => Illuminate\Support\Facades\File::class,
+        'Gate' => Illuminate\Support\Facades\Gate::class,
+        'Hash' => Illuminate\Support\Facades\Hash::class,
+        'Lang' => Illuminate\Support\Facades\Lang::class,
+        'Log' => Illuminate\Support\Facades\Log::class,
+        'Mail' => Illuminate\Support\Facades\Mail::class,
+        'Password' => Illuminate\Support\Facades\Password::class,
+        'Queue' => Illuminate\Support\Facades\Queue::class,
+        'Redirect' => Illuminate\Support\Facades\Redirect::class,
+        'Redis' => Illuminate\Support\Facades\Redis::class,
+        'Request' => Illuminate\Support\Facades\Request::class,
+        'Response' => Illuminate\Support\Facades\Response::class,
+        'Route' => Illuminate\Support\Facades\Route::class,
+        'Schema' => Illuminate\Support\Facades\Schema::class,
+        'Session' => Illuminate\Support\Facades\Session::class,
+        'Storage' => Illuminate\Support\Facades\Storage::class,
+        'URL' => Illuminate\Support\Facades\URL::class,
+        'Validator' => Illuminate\Support\Facades\Validator::class,
+        'View' => Illuminate\Support\Facades\View::class,
+
+        'Debugbar'  => Barryvdh\Debugbar\Facade::class,
+        'Twitter'   => Thujohn\Twitter\Facades\Twitter::class,
+
+    ],
+
+];
diff --git a/config/auth.php b/config/auth.php
new file mode 100644
index 00000000..3fa7f491
--- /dev/null
+++ b/config/auth.php
@@ -0,0 +1,107 @@
+ [
+        'guard' => 'web',
+        'passwords' => 'users',
+    ],
+
+    /*
+    |--------------------------------------------------------------------------
+    | Authentication Guards
+    |--------------------------------------------------------------------------
+    |
+    | Next, you may define every authentication guard for your application.
+    | Of course, a great default configuration has been defined for you
+    | here which uses session storage and the Eloquent user provider.
+    |
+    | All authentication drivers have a user provider. This defines how the
+    | users are actually retrieved out of your database or other storage
+    | mechanisms used by this application to persist your user's data.
+    |
+    | Supported: "session", "token"
+    |
+    */
+
+    'guards' => [
+        'web' => [
+            'driver' => 'session',
+            'provider' => 'users',
+        ],
+
+        'api' => [
+            'driver' => 'token',
+            'provider' => 'users',
+        ],
+    ],
+
+    /*
+    |--------------------------------------------------------------------------
+    | User Providers
+    |--------------------------------------------------------------------------
+    |
+    | All authentication drivers have a user provider. This defines how the
+    | users are actually retrieved out of your database or other storage
+    | mechanisms used by this application to persist your user's data.
+    |
+    | If you have multiple user tables or models you may configure multiple
+    | sources which represent each model / table. These sources may then
+    | be assigned to any extra authentication guards you have defined.
+    |
+    | Supported: "database", "eloquent"
+    |
+    */
+
+    'providers' => [
+        'users' => [
+            'driver' => 'eloquent',
+            'model' => App\User::class,
+        ],
+
+        // 'users' => [
+        //     'driver' => 'database',
+        //     'table' => 'users',
+        // ],
+    ],
+
+    /*
+    |--------------------------------------------------------------------------
+    | Resetting Passwords
+    |--------------------------------------------------------------------------
+    |
+    | Here you may set the options for resetting passwords including the view
+    | that is your password reset e-mail. You may also set the name of the
+    | table that maintains all of the reset tokens for your application.
+    |
+    | You may specify multiple password reset configurations if you have more
+    | than one user table or model in the application and you want to have
+    | separate password reset settings based on the specific user types.
+    |
+    | The expire time is the number of minutes that the reset token should be
+    | considered valid. This security feature keeps tokens short-lived so
+    | they have less time to be guessed. You may change this as needed.
+    |
+    */
+
+    'passwords' => [
+        'users' => [
+            'provider' => 'users',
+            'email' => 'auth.emails.password',
+            'table' => 'password_resets',
+            'expire' => 60,
+        ],
+    ],
+
+];
diff --git a/config/broadcasting.php b/config/broadcasting.php
new file mode 100644
index 00000000..abaaac32
--- /dev/null
+++ b/config/broadcasting.php
@@ -0,0 +1,52 @@
+ env('BROADCAST_DRIVER', 'pusher'),
+
+    /*
+    |--------------------------------------------------------------------------
+    | Broadcast Connections
+    |--------------------------------------------------------------------------
+    |
+    | Here you may define all of the broadcast connections that will be used
+    | to broadcast events to other systems or over websockets. Samples of
+    | each available type of connection are provided inside this array.
+    |
+    */
+
+    'connections' => [
+
+        'pusher' => [
+            'driver' => 'pusher',
+            'key' => env('PUSHER_KEY'),
+            'secret' => env('PUSHER_SECRET'),
+            'app_id' => env('PUSHER_APP_ID'),
+            'options' => [
+                //
+            ],
+        ],
+
+        'redis' => [
+            'driver' => 'redis',
+            'connection' => 'default',
+        ],
+
+        'log' => [
+            'driver' => 'log',
+        ],
+
+    ],
+
+];
diff --git a/config/cache.php b/config/cache.php
new file mode 100644
index 00000000..3ffa840b
--- /dev/null
+++ b/config/cache.php
@@ -0,0 +1,81 @@
+ env('CACHE_DRIVER', 'file'),
+
+    /*
+    |--------------------------------------------------------------------------
+    | Cache Stores
+    |--------------------------------------------------------------------------
+    |
+    | Here you may define all of the cache "stores" for your application as
+    | well as their drivers. You may even define multiple stores for the
+    | same cache driver to group types of items stored in your caches.
+    |
+    */
+
+    'stores' => [
+
+        'apc' => [
+            'driver' => 'apc',
+        ],
+
+        'array' => [
+            'driver' => 'array',
+        ],
+
+        'database' => [
+            'driver' => 'database',
+            'table' => 'cache',
+            'connection' => null,
+        ],
+
+        'file' => [
+            'driver' => 'file',
+            'path' => storage_path('framework/cache'),
+        ],
+
+        'memcached' => [
+            'driver' => 'memcached',
+            'servers' => [
+                [
+                    'host' => env('MEMCACHED_HOST', '127.0.0.1'),
+                    'port' => env('MEMCACHED_PORT', 11211),
+                    'weight' => 100,
+                ],
+            ],
+        ],
+
+        'redis' => [
+            'driver' => 'redis',
+            'connection' => 'default',
+        ],
+
+    ],
+
+    /*
+    |--------------------------------------------------------------------------
+    | Cache Key Prefix
+    |--------------------------------------------------------------------------
+    |
+    | When utilizing a RAM based store such as APC or Memcached, there might
+    | be other applications utilizing the same cache. So, we'll specify a
+    | value to get prefixed to all our keys so we can avoid collisions.
+    |
+    */
+
+    'prefix' => 'laravel',
+
+];
diff --git a/config/compile.php b/config/compile.php
new file mode 100644
index 00000000..04807eac
--- /dev/null
+++ b/config/compile.php
@@ -0,0 +1,35 @@
+ [
+        //
+    ],
+
+    /*
+    |--------------------------------------------------------------------------
+    | Compiled File Providers
+    |--------------------------------------------------------------------------
+    |
+    | Here you may list service providers which define a "compiles" function
+    | that returns additional files that should be compiled, providing an
+    | easy way to get common files from any packages you are utilizing.
+    |
+    */
+
+    'providers' => [
+        //
+    ],
+
+];
diff --git a/config/database.php b/config/database.php
new file mode 100644
index 00000000..598a7ff3
--- /dev/null
+++ b/config/database.php
@@ -0,0 +1,131 @@
+ PDO::FETCH_CLASS,
+
+    /*
+    |--------------------------------------------------------------------------
+    | Default Database Connection Name
+    |--------------------------------------------------------------------------
+    |
+    | Here you may specify which of the database connections below you wish
+    | to use as your default connection for all database work. Of course
+    | you may use many connections at once using the Database library.
+    |
+    */
+
+    'default' => env('DB_CONNECTION', 'mysql'),
+
+    /*
+    |--------------------------------------------------------------------------
+    | Database Connections
+    |--------------------------------------------------------------------------
+    |
+    | Here are each of the database connections setup for your application.
+    | Of course, examples of configuring each database platform that is
+    | supported by Laravel is shown below to make development simple.
+    |
+    |
+    | All database work in Laravel is done through the PHP PDO facilities
+    | so make sure you have the driver for your particular database of
+    | choice installed on your machine before you begin development.
+    |
+    */
+
+    'connections' => [
+
+        'sqlite' => [
+            'driver' => 'sqlite',
+            'database' => env('DB_DATABASE', database_path('database.sqlite')),
+            'prefix' => '',
+        ],
+
+        'mysql' => [
+            'driver'    => 'mysql',
+            'host'      => env('DB_HOST', 'localhost'),
+            'port'      => env('DB_PORT', '3306'),
+            'database'  => env('DB_DATABASE', 'forge'),
+            'username'  => env('DB_USERNAME', 'forge'),
+            'password'  => env('DB_PASSWORD', ''),
+            'charset'   => 'utf8mb4',
+            'collation' => 'utf8mb4_unicode_ci',
+            'prefix'    => '',
+            'strict'    => false,
+            'engine'    => null,
+        ],
+
+        'pgsql' => [
+            'driver'    => 'pgsql',
+            'host'      => env('DB_HOST', 'localhost'),
+            'port'      => env('DB_PORT', '5432'),
+            'database'  => env('DB_DATABASE', 'forge'),
+            'username'  => env('DB_USERNAME', 'forge'),
+            'password'  => env('DB_PASSWORD', ''),
+            'charset'   => 'utf8',
+            'prefix'    => '',
+            'schema'    => 'public',
+        ],
+
+        'travis' => [
+            'driver'   => 'pgsql',
+            'host'     => 'localhost',
+            'database' => 'travis_ci_test',
+            'username' => 'travis',
+            'password' => '',
+            'charset'  => 'utf8',
+            'prefix'   => '',
+            'schema'   => 'public',
+        ]
+
+    ],
+
+    /*
+    |--------------------------------------------------------------------------
+    | Migration Repository Table
+    |--------------------------------------------------------------------------
+    |
+    | This table keeps track of all the migrations that have already run for
+    | your application. Using this information, we can determine which of
+    | the migrations on disk haven't actually been run in the database.
+    |
+    */
+
+    'migrations' => 'migrations',
+
+    /*
+    |--------------------------------------------------------------------------
+    | Redis Databases
+    |--------------------------------------------------------------------------
+    |
+    | Redis is an open source, fast, and advanced key-value store that also
+    | provides a richer set of commands than a typical key-value systems
+    | such as APC or Memcached. Laravel makes it easy to dig right in.
+    |
+    */
+
+    'redis' => [
+
+        'cluster' => false,
+
+        'default' => [
+            'host' => env('REDIS_HOST', 'localhost'),
+            'password' => env('REDIS_PASSWORD', null),
+            'port' => env('REDIS_PORT', 6379),
+            'database' => 0,
+        ],
+
+    ],
+
+];
diff --git a/config/debugbar.php b/config/debugbar.php
new file mode 100644
index 00000000..7d46a634
--- /dev/null
+++ b/config/debugbar.php
@@ -0,0 +1,145 @@
+ null,
+
+    /*
+     |--------------------------------------------------------------------------
+     | Storage settings
+     |--------------------------------------------------------------------------
+     |
+     | DebugBar stores data for session/ajax requests.
+     | You can disable this, so the debugbar stores data in headers/session,
+     | but this can cause problems with large data collectors.
+     | By default, file storage (in the storage folder) is used. Redis and PDO
+     | can also be used. For PDO, run the package migrations first.
+     |
+     */
+    'storage' => array(
+        'enabled' => true,
+        'driver' => 'file', // redis, file, pdo
+        'path' => storage_path() . '/debugbar', // For file driver
+        'connection' => null,   // Leave null for default connection (Redis/PDO)
+    ),
+
+    /*
+     |--------------------------------------------------------------------------
+     | Vendors
+     |--------------------------------------------------------------------------
+     |
+     | Vendor files are included by default, but can be set to false.
+     | This can also be set to 'js' or 'css', to only include javascript or css vendor files.
+     | Vendor files are for css: font-awesome (including fonts) and highlight.js (css files)
+     | and for js: jquery and and highlight.js
+     | So if you want syntax highlighting, set it to true.
+     | jQuery is set to not conflict with existing jQuery scripts.
+     |
+     */
+
+    'include_vendors' => true,
+
+    /*
+     |--------------------------------------------------------------------------
+     | Capture Ajax Requests
+     |--------------------------------------------------------------------------
+     |
+     | The Debugbar can capture Ajax requests and display them. If you don't want this (ie. because of errors),
+     | you can use this option to disable sending the data through the headers.
+     |
+     */
+
+    'capture_ajax' => true,
+
+    /*
+     |--------------------------------------------------------------------------
+     | DataCollectors
+     |--------------------------------------------------------------------------
+     |
+     | Enable/disable DataCollectors
+     |
+     */
+
+    'collectors' => array(
+        'phpinfo'         => true,  // Php version
+        'messages'        => true,  // Messages
+        'time'            => true,  // Time Datalogger
+        'memory'          => true,  // Memory usage
+        'exceptions'      => true,  // Exception displayer
+        'log'             => true,  // Logs from Monolog (merged in messages if enabled)
+        'db'              => true,  // Show database (PDO) queries and bindings
+        'views'           => true,  // Views with their data
+        'route'           => true,  // Current route information
+        'laravel'         => false, // Laravel version and environment
+        'events'          => false, // All events fired
+        'default_request' => false, // Regular or special Symfony request logger
+        'symfony_request' => true,  // Only one can be enabled..
+        'mail'            => true,  // Catch mail messages
+        'logs'            => false, // Add the latest log messages
+        'files'           => false, // Show the included files
+        'config'          => false, // Display config settings
+        'auth'            => false, // Display Laravel authentication status
+        'session'         => false, // Display session data in a separate tab
+    ),
+
+    /*
+     |--------------------------------------------------------------------------
+     | Extra options
+     |--------------------------------------------------------------------------
+     |
+     | Configure some DataCollectors
+     |
+     */
+
+    'options' => array(
+        'auth' => array(
+            'show_name' => false,   // Also show the users name/email in the debugbar
+        ),
+        'db' => array(
+            'with_params'       => true,   // Render SQL with the parameters substituted
+            'timeline'          => false,  // Add the queries to the timeline
+            'backtrace'         => false,  // EXPERIMENTAL: Use a backtrace to find the origin of the query in your files.
+            'explain' => array(            // EXPERIMENTAL: Show EXPLAIN output on queries
+                'enabled' => false,
+                'types' => array('SELECT'), // array('SELECT', 'INSERT', 'UPDATE', 'DELETE'); for MySQL 5.6.3+
+            ),
+            'hints'             => true,    // Show hints for common mistakes
+        ),
+        'mail' => array(
+            'full_log' => false
+        ),
+        'views' => array(
+            'data' => false,    //Note: Can slow down the application, because the data can be quite large..
+        ),
+        'route' => array(
+            'label' => true  // show complete route on bar
+        ),
+        'logs' => array(
+            'file' => null
+        ),
+    ),
+
+    /*
+     |--------------------------------------------------------------------------
+     | Inject Debugbar in Response
+     |--------------------------------------------------------------------------
+     |
+     | Usually, the debugbar is added just before , by listening to the
+     | Response after the App is done. If you disable this, you have to add them
+     | in your template yourself. See http://phpdebugbar.com/docs/rendering.html
+     |
+     */
+
+    'inject' => true,
+
+);
diff --git a/config/filesystems.php b/config/filesystems.php
new file mode 100644
index 00000000..1c46f7c4
--- /dev/null
+++ b/config/filesystems.php
@@ -0,0 +1,72 @@
+ 'local',
+
+    /*
+    |--------------------------------------------------------------------------
+    | Default Cloud Filesystem Disk
+    |--------------------------------------------------------------------------
+    |
+    | Many applications store files both locally and in the cloud. For this
+    | reason, you may specify a default "cloud" driver here. This driver
+    | will be bound as the Cloud disk implementation in the container.
+    |
+    */
+
+    'cloud' => 's3',
+
+    /*
+    |--------------------------------------------------------------------------
+    | Filesystem Disks
+    |--------------------------------------------------------------------------
+    |
+    | Here you may configure as many filesystem "disks" as you wish, and you
+    | may even configure multiple disks of the same driver. Defaults have
+    | been setup for each driver as an example of the required options.
+    |
+    */
+
+    'disks' => [
+
+        'local' => [
+            'driver' => 'local',
+            'root' => storage_path('app'),
+        ],
+
+        'public' => [
+            'driver' => 'local',
+            'root' => storage_path('app/public'),
+            'visibility' => 'public',
+        ],
+
+        's3' => [
+            'driver' => 's3',
+            'key'    => env('AWS_S3_KEY'),
+            'secret' => env('AWS_S3_SECRET'),
+            'region' => env('AWS_S3_REGION'),
+            'bucket' => env('AWS_S3_BUCKET'),
+        ],
+
+        'media' => [
+            'driver' => 'local',
+            'root'   => public_path() . '/media',
+        ],
+
+    ],
+
+];
diff --git a/config/laravel-medialibrary.php b/config/laravel-medialibrary.php
new file mode 100644
index 00000000..c6abd4c5
--- /dev/null
+++ b/config/laravel-medialibrary.php
@@ -0,0 +1,40 @@
+ 'media',
+
+    /*
+     * The maximum file size of an item in bytes. Adding a file
+     * that is larger will result in an exception.
+     */
+    'max_file_size' => 1024 * 1024 * 10,
+
+    /*
+     * This queue will used to generate derived images.
+     * Leave empty to use the default queue.
+     */
+    'queue_name' => '',
+
+    /*
+     * The class name of the media model to be used.
+     */
+    'media_model' => Spatie\MediaLibrary\Media::class,
+
+    /*
+     * When urls to files get generated this class will be called. Leave empty
+     * if your files are stored locally above the site root or on s3.
+     */
+    'custom_url_generator_class' => '',
+
+    's3' => [
+        /*
+         * The domain that should be prepended when generating urls.
+         */
+        'domain' => env('AWS_S3_URL'),
+    ],
+];
diff --git a/config/mail.php b/config/mail.php
new file mode 100644
index 00000000..a0765885
--- /dev/null
+++ b/config/mail.php
@@ -0,0 +1,112 @@
+ env('MAIL_DRIVER', 'smtp'),
+
+    /*
+    |--------------------------------------------------------------------------
+    | SMTP Host Address
+    |--------------------------------------------------------------------------
+    |
+    | Here you may provide the host address of the SMTP server used by your
+    | applications. A default option is provided that is compatible with
+    | the Mailgun mail service which will provide reliable deliveries.
+    |
+    */
+
+    'host' => env('MAIL_HOST', 'smtp.mailgun.org'),
+
+    /*
+    |--------------------------------------------------------------------------
+    | SMTP Host Port
+    |--------------------------------------------------------------------------
+    |
+    | This is the SMTP port used by your application to deliver e-mails to
+    | users of the application. Like the host we have set this value to
+    | stay compatible with the Mailgun e-mail application by default.
+    |
+    */
+
+    'port' => env('MAIL_PORT', 587),
+
+    /*
+    |--------------------------------------------------------------------------
+    | Global "From" Address
+    |--------------------------------------------------------------------------
+    |
+    | You may wish for all e-mails sent by your application to be sent from
+    | the same address. Here, you may specify a name and address that is
+    | used globally for all e-mails that are sent by your application.
+    |
+    */
+
+    'from' => ['address' => null, 'name' => null],
+
+    /*
+    |--------------------------------------------------------------------------
+    | E-Mail Encryption Protocol
+    |--------------------------------------------------------------------------
+    |
+    | Here you may specify the encryption protocol that should be used when
+    | the application send e-mail messages. A sensible default using the
+    | transport layer security protocol should provide great security.
+    |
+    */
+
+    'encryption' => env('MAIL_ENCRYPTION', 'tls'),
+
+    /*
+    |--------------------------------------------------------------------------
+    | SMTP Server Username
+    |--------------------------------------------------------------------------
+    |
+    | If your SMTP server requires a username for authentication, you should
+    | set it here. This will get used to authenticate with your server on
+    | connection. You may also set the "password" value below this one.
+    |
+    */
+
+    'username' => env('MAIL_USERNAME'),
+
+    /*
+    |--------------------------------------------------------------------------
+    | SMTP Server Password
+    |--------------------------------------------------------------------------
+    |
+    | Here you may set the password required by your SMTP server to send out
+    | messages from your application. This will be given to the server on
+    | connection so that the application will be able to send messages.
+    |
+    */
+
+    'password' => env('MAIL_PASSWORD'),
+
+    /*
+    |--------------------------------------------------------------------------
+    | Sendmail System Path
+    |--------------------------------------------------------------------------
+    |
+    | When using the "sendmail" driver to send e-mails, we will need to know
+    | the path to where Sendmail lives on this server. A default path has
+    | been provided here, which will work well on most of your systems.
+    |
+    */
+
+    'sendmail' => '/usr/sbin/sendmail -bs',
+
+];
diff --git a/config/queue.php b/config/queue.php
new file mode 100644
index 00000000..d0f732a6
--- /dev/null
+++ b/config/queue.php
@@ -0,0 +1,85 @@
+ env('QUEUE_DRIVER', 'sync'),
+
+    /*
+    |--------------------------------------------------------------------------
+    | Queue Connections
+    |--------------------------------------------------------------------------
+    |
+    | Here you may configure the connection information for each server that
+    | is used by your application. A default configuration has been added
+    | for each back-end shipped with Laravel. You are free to add more.
+    |
+    */
+
+    'connections' => [
+
+        'sync' => [
+            'driver' => 'sync',
+        ],
+
+        'database' => [
+            'driver' => 'database',
+            'table' => 'jobs',
+            'queue' => 'default',
+            'expire' => 60,
+        ],
+
+        'beanstalkd' => [
+            'driver' => 'beanstalkd',
+            'host' => 'localhost',
+            'queue' => 'default',
+            'ttr' => 60,
+        ],
+
+        'sqs' => [
+            'driver' => 'sqs',
+            'key' => 'your-public-key',
+            'secret' => 'your-secret-key',
+            'prefix' => 'https://sqs.us-east-1.amazonaws.com/your-account-id',
+            'queue' => 'your-queue-name',
+            'region' => 'us-east-1',
+        ],
+
+        'redis' => [
+            'driver' => 'redis',
+            'connection' => 'default',
+            'queue' => 'default',
+            'expire' => 60,
+        ],
+
+    ],
+
+    /*
+    |--------------------------------------------------------------------------
+    | Failed Queue Jobs
+    |--------------------------------------------------------------------------
+    |
+    | These options configure the behavior of failed queue job logging so you
+    | can control which database and table are used to store the jobs that
+    | have failed. You may change them to any database / table you wish.
+    |
+    */
+
+    'failed' => [
+        'database' => env('DB_CONNECTION', 'mysql'),
+        'table' => 'failed_jobs',
+    ],
+
+];
diff --git a/config/services.php b/config/services.php
new file mode 100644
index 00000000..287b1186
--- /dev/null
+++ b/config/services.php
@@ -0,0 +1,38 @@
+ [
+        'domain' => env('MAILGUN_DOMAIN'),
+        'secret' => env('MAILGUN_SECRET'),
+    ],
+
+    'ses' => [
+        'key' => env('SES_KEY'),
+        'secret' => env('SES_SECRET'),
+        'region' => 'us-east-1',
+    ],
+
+    'sparkpost' => [
+        'secret' => env('SPARKPOST_SECRET'),
+    ],
+
+    'stripe' => [
+        'model' => App\User::class,
+        'key' => env('STRIPE_KEY'),
+        'secret' => env('STRIPE_SECRET'),
+    ],
+
+];
diff --git a/config/session.php b/config/session.php
new file mode 100644
index 00000000..33f62e49
--- /dev/null
+++ b/config/session.php
@@ -0,0 +1,166 @@
+ env('SESSION_DRIVER', 'file'),
+
+    /*
+    |--------------------------------------------------------------------------
+    | Session Lifetime
+    |--------------------------------------------------------------------------
+    |
+    | Here you may specify the number of minutes that you wish the session
+    | to be allowed to remain idle before it expires. If you want them
+    | to immediately expire on the browser closing, set that option.
+    |
+    */
+
+    'lifetime' => 60 * 24 * 7,
+
+    'expire_on_close' => false,
+
+    /*
+    |--------------------------------------------------------------------------
+    | Session Encryption
+    |--------------------------------------------------------------------------
+    |
+    | This option allows you to easily specify that all of your session data
+    | should be encrypted before it is stored. All encryption will be run
+    | automatically by Laravel and you can use the Session like normal.
+    |
+    */
+
+    'encrypt' => false,
+
+    /*
+    |--------------------------------------------------------------------------
+    | Session File Location
+    |--------------------------------------------------------------------------
+    |
+    | When using the native session driver, we need a location where session
+    | files may be stored. A default has been set for you but a different
+    | location may be specified. This is only needed for file sessions.
+    |
+    */
+
+    'files' => storage_path('framework/sessions'),
+
+    /*
+    |--------------------------------------------------------------------------
+    | Session Database Connection
+    |--------------------------------------------------------------------------
+    |
+    | When using the "database" or "redis" session drivers, you may specify a
+    | connection that should be used to manage these sessions. This should
+    | correspond to a connection in your database configuration options.
+    |
+    */
+
+    'connection' => null,
+
+    /*
+    |--------------------------------------------------------------------------
+    | Session Database Table
+    |--------------------------------------------------------------------------
+    |
+    | When using the "database" session driver, you may specify the table we
+    | should use to manage the sessions. Of course, a sensible default is
+    | provided for you; however, you are free to change this as needed.
+    |
+    */
+
+    'table' => 'sessions',
+
+    /*
+    |--------------------------------------------------------------------------
+    | Session Sweeping Lottery
+    |--------------------------------------------------------------------------
+    |
+    | Some session drivers must manually sweep their storage location to get
+    | rid of old sessions from storage. Here are the chances that it will
+    | happen on a given request. By default, the odds are 2 out of 100.
+    |
+    */
+
+    'lottery' => [2, 100],
+
+    /*
+    |--------------------------------------------------------------------------
+    | Session Cookie Name
+    |--------------------------------------------------------------------------
+    |
+    | Here you may change the name of the cookie used to identify a session
+    | instance by ID. The name specified here will get used every time a
+    | new session cookie is created by the framework for every driver.
+    |
+    */
+
+    'cookie' => 'laravel_session',
+
+    /*
+    |--------------------------------------------------------------------------
+    | Session Cookie Path
+    |--------------------------------------------------------------------------
+    |
+    | The session cookie path determines the path for which the cookie will
+    | be regarded as available. Typically, this will be the root path of
+    | your application but you are free to change this when necessary.
+    |
+    */
+
+    'path' => '/',
+
+    /*
+    |--------------------------------------------------------------------------
+    | Session Cookie Domain
+    |--------------------------------------------------------------------------
+    |
+    | Here you may change the domain of the cookie used to identify a session
+    | in your application. This will determine which domains the cookie is
+    | available to in your application. A sensible default has been set.
+    |
+    */
+
+    'domain' => null,
+
+    /*
+    |--------------------------------------------------------------------------
+    | HTTPS Only Cookies
+    |--------------------------------------------------------------------------
+    |
+    | By setting this option to true, session cookies will only be sent back
+    | to the server if the browser has a HTTPS connection. This will keep
+    | the cookie from being sent to you if it can not be done securely.
+    |
+    */
+
+    'secure' => true,
+
+    /*
+    |--------------------------------------------------------------------------
+    | HTTP Access Only
+    |--------------------------------------------------------------------------
+    |
+    | Setting this value to true will prevent JavaScript from accessing the
+    | value of the cookie and the cookie will only be accessible through
+    | the HTTP protocol. You are free to modify this option if needed.
+    |
+    */
+
+    'http_only' => true,
+
+];
diff --git a/config/ttwitter.php b/config/ttwitter.php
new file mode 100644
index 00000000..8c6e4ec2
--- /dev/null
+++ b/config/ttwitter.php
@@ -0,0 +1,18 @@
+ 'api.twitter.com',
+		'API_VERSION'         => '1.1',
+		'AUTHENTICATE_URL'    => 'https://api.twitter.com/oauth/authenticate',
+		'AUTHORIZE_URL'       => 'https://api.twitter.com/oauth/authorize',
+		'ACCESS_TOKEN_URL'    => 'oauth/access_token',
+		'REQUEST_TOKEN_URL'   => 'oauth/request_token',
+		'USE_SSL'             => true,
+
+		'CONSUMER_KEY'        => env('TWITTER_CONSUMER_KEY'),
+		'CONSUMER_SECRET'     => env('TWITTER_CONSUMER_SECRET'),
+		'ACCESS_TOKEN'        => env('TWITTER_ACCESS_TOKEN'),
+		'ACCESS_TOKEN_SECRET' => env('TWITTER_ACCESS_TOKEN_SECRET'),
+	];
diff --git a/config/url.php b/config/url.php
new file mode 100644
index 00000000..96767e3f
--- /dev/null
+++ b/config/url.php
@@ -0,0 +1,11 @@
+ env('APP_LONGURL', 'jonnybarnes.uk'),
+    'shorturl' => env('APP_SHORTURL', 'jmb.so')
+];
\ No newline at end of file
diff --git a/config/view.php b/config/view.php
new file mode 100644
index 00000000..e193ab61
--- /dev/null
+++ b/config/view.php
@@ -0,0 +1,33 @@
+ [
+        realpath(base_path('resources/views')),
+    ],
+
+    /*
+    |--------------------------------------------------------------------------
+    | Compiled View Path
+    |--------------------------------------------------------------------------
+    |
+    | This option determines where all the compiled Blade templates will be
+    | stored for your application. Typically, this is within the storage
+    | directory. However, as usual, you are free to change this value.
+    |
+    */
+
+    'compiled' => realpath(storage_path('framework/views')),
+
+];
diff --git a/database/.gitignore b/database/.gitignore
new file mode 100644
index 00000000..9b1dffd9
--- /dev/null
+++ b/database/.gitignore
@@ -0,0 +1 @@
+*.sqlite
diff --git a/database/factories/ModelFactory.php b/database/factories/ModelFactory.php
new file mode 100644
index 00000000..df9993c9
--- /dev/null
+++ b/database/factories/ModelFactory.php
@@ -0,0 +1,28 @@
+define(App\User::class, function (Faker\Generator $faker) {
+    return [
+        'name' => $faker->name,
+        'email' => $faker->safeEmail,
+        'password' => bcrypt(str_random(10)),
+        'remember_token' => str_random(10),
+    ];
+});
+
+$factory->define(App\Note::class, function (Faker\Generator $faker) {
+    return [
+        'note' => $faker->paragraph,
+        'tweet_id' => $faker->randomNumber(9),
+    ];
+});
diff --git a/database/migrations/.gitkeep b/database/migrations/.gitkeep
new file mode 100644
index 00000000..8b137891
--- /dev/null
+++ b/database/migrations/.gitkeep
@@ -0,0 +1 @@
+
diff --git a/database/migrations/2015_02_28_132629_create_articles_table.php b/database/migrations/2015_02_28_132629_create_articles_table.php
new file mode 100644
index 00000000..43277762
--- /dev/null
+++ b/database/migrations/2015_02_28_132629_create_articles_table.php
@@ -0,0 +1,38 @@
+increments('id');
+                $table->string('titleurl', 50)->unique();
+                $table->string('url', 120)->nullable();
+                $table->string('title');
+                $table->longText('main');
+                $table->tinyInteger('published')->default(0);
+                $table->timestamps();
+                $table->softDeletes();
+            });
+        }
+    }
+
+    /**
+     * Reverse the migrations.
+     *
+     * @return void
+     */
+    public function down()
+    {
+        Schema::drop('articles');
+    }
+}
diff --git a/database/migrations/2015_02_28_144939_create_notes_table.php b/database/migrations/2015_02_28_144939_create_notes_table.php
new file mode 100644
index 00000000..33e22669
--- /dev/null
+++ b/database/migrations/2015_02_28_144939_create_notes_table.php
@@ -0,0 +1,40 @@
+increments('id');
+                $table->text('note');
+                $table->string('in_reply_to')->nullable();
+                $table->string('shorturl', 20)->nullable();
+                $table->string('location')->nullable();
+                $table->tinyInteger('photo')->nullable();
+                $table->string('tweet_id')->nullable();
+                $table->string('client_id')->nullable();
+                $table->timestamps();
+                $table->softDeletes();
+            });
+        }
+    }
+
+    /**
+     * Reverse the migrations.
+     *
+     * @return void
+     */
+    public function down()
+    {
+        Schema::drop('notes');
+    }
+}
diff --git a/database/migrations/2015_03_02_084342_create_tags_table.php b/database/migrations/2015_03_02_084342_create_tags_table.php
new file mode 100644
index 00000000..0e1175ea
--- /dev/null
+++ b/database/migrations/2015_03_02_084342_create_tags_table.php
@@ -0,0 +1,31 @@
+increments('id');
+            $table->string('tag');
+            $table->timestamps();
+        });
+    }
+
+    /**
+     * Reverse the migrations.
+     *
+     * @return void
+     */
+    public function down()
+    {
+        Schema::drop('tags');
+    }
+}
diff --git a/database/migrations/2015_03_02_084956_create_note_tag_table.php b/database/migrations/2015_03_02_084956_create_note_tag_table.php
new file mode 100644
index 00000000..831cff30
--- /dev/null
+++ b/database/migrations/2015_03_02_084956_create_note_tag_table.php
@@ -0,0 +1,33 @@
+increments('id');
+            $table->integer('note_id')->unsigned();
+            $table->integer('tag_id')->unsigned();
+            $table->foreign('note_id')->references('id')->on('notes');
+            $table->foreign('tag_id')->references('id')->on('tags');
+        });
+    }
+
+    /**
+     * Reverse the migrations.
+     *
+     * @return void
+     */
+    public function down()
+    {
+        Schema::drop('note_tag');
+    }
+}
diff --git a/database/migrations/2015_03_02_105623_create_contacts_table.php b/database/migrations/2015_03_02_105623_create_contacts_table.php
new file mode 100644
index 00000000..b0b1e3ec
--- /dev/null
+++ b/database/migrations/2015_03_02_105623_create_contacts_table.php
@@ -0,0 +1,34 @@
+increments('id');
+            $table->string('nick');
+            $table->string('name');
+            $table->string('homepage')->nullable();
+            $table->string('twitter')->nullable();
+            $table->timestamps();
+        });
+    }
+
+    /**
+     * Reverse the migrations.
+     *
+     * @return void
+     */
+    public function down()
+    {
+        Schema::drop('contacts');
+    }
+}
diff --git a/database/migrations/2015_03_02_114340_create_web_mentions_table.php b/database/migrations/2015_03_02_114340_create_web_mentions_table.php
new file mode 100644
index 00000000..edbbfba3
--- /dev/null
+++ b/database/migrations/2015_03_02_114340_create_web_mentions_table.php
@@ -0,0 +1,39 @@
+increments('id');
+            $table->string('source');
+            $table->string('target');
+            $table->integer('commentable_id')->nullable();
+            $table->string('commentable_type')->nullable();
+            $table->string('type')->nullable();
+            $table->text('content');
+            $table->tinyInteger('verified')->default(1);
+            $table->timestamps();
+            $table->softDeletes();
+        });
+    }
+
+    /**
+     * Reverse the migrations.
+     *
+     * @return void
+     */
+    public function down()
+    {
+        Schema::drop('webmentions');
+    }
+}
diff --git a/database/migrations/2015_07_17_111512_create_clients_table.php b/database/migrations/2015_07_17_111512_create_clients_table.php
new file mode 100644
index 00000000..4821d16b
--- /dev/null
+++ b/database/migrations/2015_07_17_111512_create_clients_table.php
@@ -0,0 +1,32 @@
+increments('id');
+            $table->string('client_url');
+            $table->string('client_name');
+            $table->timestamps();
+        });
+    }
+
+    /**
+     * Reverse the migrations.
+     *
+     * @return void
+     */
+    public function down()
+    {
+        Schema::drop('clients');
+    }
+}
diff --git a/database/migrations/2015_07_22_084423_create_failed_jobs_table.php b/database/migrations/2015_07_22_084423_create_failed_jobs_table.php
new file mode 100644
index 00000000..c1ba41b4
--- /dev/null
+++ b/database/migrations/2015_07_22_084423_create_failed_jobs_table.php
@@ -0,0 +1,33 @@
+increments('id');
+            $table->text('connection');
+            $table->text('queue');
+            $table->longText('payload');
+            $table->timestamp('failed_at');
+        });
+    }
+
+    /**
+     * Reverse the migrations.
+     *
+     * @return void
+     */
+    public function down()
+    {
+        Schema::drop('failed_jobs');
+    }
+}
diff --git a/database/migrations/2015_10_08_155111_create_media_table.php b/database/migrations/2015_10_08_155111_create_media_table.php
new file mode 100644
index 00000000..15a3ce03
--- /dev/null
+++ b/database/migrations/2015_10_08_155111_create_media_table.php
@@ -0,0 +1,35 @@
+increments('id');
+            $table->morphs('model');
+            $table->string('collection_name');
+            $table->string('name');
+            $table->string('file_name');
+            $table->string('disk');
+            $table->unsignedInteger('size');
+            $table->text('manipulations');
+            $table->text('custom_properties');
+            $table->unsignedInteger('order_column')->nullable();
+            $table->timestamps();
+        });
+    }
+    /**
+     * Reverse the migrations.
+     */
+    public function down()
+    {
+        Schema::drop('media');
+    }
+}
diff --git a/database/migrations/2015_11_07_130637_create_places_table.php b/database/migrations/2015_11_07_130637_create_places_table.php
new file mode 100644
index 00000000..3d6927c0
--- /dev/null
+++ b/database/migrations/2015_11_07_130637_create_places_table.php
@@ -0,0 +1,35 @@
+increments('id');
+            $table->string('name');
+            $table->string('slug')->unique();
+            $table->text('description')->nullable();
+            $table->point('location');
+            $table->polygon('polygon')->nullable();
+            $table->timestamps();
+        });
+    }
+
+    /**
+     * Reverse the migrations.
+     *
+     * @return void
+     */
+    public function down()
+    {
+        Schema::drop('places');
+    }
+}
diff --git a/database/migrations/2015_11_19_221933_add_place_relation_to_notes.php b/database/migrations/2015_11_19_221933_add_place_relation_to_notes.php
new file mode 100644
index 00000000..a999d7bd
--- /dev/null
+++ b/database/migrations/2015_11_19_221933_add_place_relation_to_notes.php
@@ -0,0 +1,33 @@
+integer('place_id')->unsigned()->nullable();
+            $table->foreign('place_id')->references('id')->on('places');
+        });
+    }
+
+    /**
+     * Reverse the migrations.
+     *
+     * @return void
+     */
+    public function down()
+    {
+        Schema::table('notes', function (Blueprint $table) {
+            $table->dropForeign('notes_place_id_foreign');
+            $table->dropColumn('place_id');
+        });
+    }
+}
diff --git a/database/seeds/.gitkeep b/database/seeds/.gitkeep
new file mode 100644
index 00000000..8b137891
--- /dev/null
+++ b/database/seeds/.gitkeep
@@ -0,0 +1 @@
+
diff --git a/database/seeds/ArticlesTableSeeder.php b/database/seeds/ArticlesTableSeeder.php
new file mode 100644
index 00000000..af99e3c2
--- /dev/null
+++ b/database/seeds/ArticlesTableSeeder.php
@@ -0,0 +1,23 @@
+insert([
+            'titleurl' => 'my-new-blog',
+            'title' => 'My New Blog',
+            'main' => 'This is my new blog. It uses `Markdown`.',
+            'published' => 1,
+            'created_at' => '2016-01-12 15:51:01',
+            'updated_at' => '2016-01-12 15:51:01',
+        ]);
+    }
+}
diff --git a/database/seeds/ClientsTableSeeder.php b/database/seeds/ClientsTableSeeder.php
new file mode 100644
index 00000000..5d833f82
--- /dev/null
+++ b/database/seeds/ClientsTableSeeder.php
@@ -0,0 +1,21 @@
+insert([
+            'client_url' => 'https://jbl5.dev/notes/new',
+            'client_name' => 'JBL5',
+            'created_at' => '2016-01-12 16:03:00',
+            'updated_at' => '2016-01-12 16:03:00',
+        ]);
+    }
+}
diff --git a/database/seeds/ContactsTableSeeder.php b/database/seeds/ContactsTableSeeder.php
new file mode 100644
index 00000000..8ce80a0b
--- /dev/null
+++ b/database/seeds/ContactsTableSeeder.php
@@ -0,0 +1,32 @@
+insert([
+            'nick' => 'tantek',
+            'name' => 'Tantek Çelik',
+            'homepage' => 'http://tantek.com',
+            'twitter' => 't',
+            'created_at' => '2016-01-12 16:11:00',
+            'updated_at' => '2016-01-12 16:11:00',
+        ]);
+
+        DB::table('contacts')->insert([
+            'nick' => 'aaron',
+            'name' => 'Aaron Parecki',
+            'homepage' => 'https://aaronparecki.com',
+            'twitter' => 'aaronpk',
+            'created_at' => '2016-01-12 16:12:00',
+            'updated_at' => '2016-01-12 16:12:00',
+        ]);
+    }
+}
diff --git a/database/seeds/DatabaseSeeder.php b/database/seeds/DatabaseSeeder.php
new file mode 100644
index 00000000..802cddc6
--- /dev/null
+++ b/database/seeds/DatabaseSeeder.php
@@ -0,0 +1,20 @@
+call(ArticlesTableSeeder::class);
+        $this->call(ClientsTableSeeder::class);
+        $this->call(ContactsTableSeeder::class);
+        $this->call(PlacesTableSeeder::class);
+        $this->call(NotesTableSeeder::class);
+    }
+}
diff --git a/database/seeds/NotesTableSeeder.php b/database/seeds/NotesTableSeeder.php
new file mode 100644
index 00000000..9fa8ccd1
--- /dev/null
+++ b/database/seeds/NotesTableSeeder.php
@@ -0,0 +1,39 @@
+create();
+        $noteWithPlace = App\Note::create([
+            'note' => 'Having a #beer at the local.'
+        ]);
+        $place = App\Place::find(1);
+        $noteWithPlace->place()->associate($place);
+        $noteWithPlace->save();
+        $noteWithContact = App\Note::create([
+            'note' => 'Hi @tantek'
+        ]);
+        $noteWithContactPlusPic = App\Note::create([
+            'note' => 'Hi @aaron',
+            'client_id' => 'https://jbl5.dev/notes/new'
+        ]);
+        $noteWithoutContact = App\Note::create([
+            'note' => 'Hi @bob',
+            'client_id' => 'https://quill.p3k.io'
+        ]);
+        //copy aaron’s profile pic in place
+        $spl = new SplFileInfo(public_path() . '/assets/profile-images/aaronparecki.com');
+        if ($spl->isDir() === false) {
+            mkdir(public_path() . '/assets/profile-images/aaronparecki.com', 0755);
+            copy(base_path() . '/tests/aaron.png', public_path() . '/assets/profile-images/aaronparecki.com/image');
+        }
+    }
+}
diff --git a/database/seeds/PlacesTableSeeder.php b/database/seeds/PlacesTableSeeder.php
new file mode 100644
index 00000000..34302d50
--- /dev/null
+++ b/database/seeds/PlacesTableSeeder.php
@@ -0,0 +1,23 @@
+insert([
+            'name' => 'The Bridgewater Pub',
+            'slug' => 'the-bridgewater-pub',
+            'description' => 'A lovely local pub with a decent selection pf cask ales',
+            'location' => 'POINT(-2.3805 53.4983)',
+            'created_at' => '2016-01-12 16:19:00',
+            'updated_at' => '2016-01-12 16:19:00',
+        ]);
+    }
+}
diff --git a/gulpfile.js b/gulpfile.js
new file mode 100644
index 00000000..6395c379
--- /dev/null
+++ b/gulpfile.js
@@ -0,0 +1,81 @@
+var gulp = require('gulp');
+var zopfli = require('gulp-zopfli');
+var brotli = require('gulp-brotli');
+var elixir = require('laravel-elixir');
+
+/*
+ |--------------------------------------------------------------------------
+ | Elixir Asset Management
+ |--------------------------------------------------------------------------
+ |
+ | Elixir provides a clean, fluent API for defining some basic Gulp tasks
+ | for your Laravel application. By default, we are compiling the Sass
+ | file for our application, as well as publishing vendor resources.
+ |
+ */
+
+elixir(function(mix) {
+    mix.sass('global.scss', 'public/assets/css');
+    mix.version([
+        'assets/css/global.css',
+        'assets/css/projects.css',
+        'assets/css/alertify.css',
+        'assets/css/sanitize.min.css',
+        'assets/css/prism.css',
+        'assets/js/form-save.js',
+        'assets/js/links.js',
+        'assets/js/maps.js',
+        'assets/js/newplace.js',
+        'assets/js/newnote.js',
+        'assets/js/fetch.js',
+        'assets/js/alertify.js',
+        'assets/js/store2.min.js',
+        'assets/js/Autolinker.min.js',
+        'assets/js/marked.min.js',
+        'assets/js/prism.js',
+    ]);
+});
+
+gulp.task('gzip-built-css', function() {
+    return gulp.src('public/build/assets/css/*.css')
+        .pipe(zopfli({ format: 'gzip', append: true }))
+        .pipe(gulp.dest('public/build/assets/css/'));
+});
+
+gulp.task('br-built-css', function() {
+    return gulp.src('public/build/assets/css/*.css')
+        .pipe(brotli.compress({mode: 1, quality: 11}))
+        .pipe(gulp.dest('public/build/assets/css/'));
+});
+
+gulp.task('gzip-built-js', function() {
+    return gulp.src('public/build/assets/js/*.js')
+        .pipe(zopfli({ format: 'gzip', append: true }))
+        .pipe(gulp.dest('public/build/assets/js/'));
+});
+
+gulp.task('br-built-js', function() {
+    return gulp.src('public/build/assets/js/*.js')
+        .pipe(brotli.compress({mode: 1, quality: 11}))
+        .pipe(gulp.dest('public/build/assets/js/'));
+});
+
+gulp.task('bower', function() {
+    //copy JS files
+    gulp.src([
+            'bower_components/fetch/fetch.js',
+            'bower_components/alertify.js/dist/js/alertify.js',
+            'bower_components/store2/dist/store2.min.js',
+            'bower_components/Autolinker.js/dist/Autolinker.min.js',
+            'bower_components/marked/marked.min.js',
+        ])
+        .pipe(gulp.dest('public/assets/js/'));
+    //copy CSS files
+    gulp.src([
+            'bower_components/alertify.js/dist/css/alertify.css',
+            'bower_components/sanitize-css/dist/sanitize.min.css',
+        ])
+        .pipe(gulp.dest('public/assets/css/'));
+});
+
+gulp.task('compress', ['gzip-built-css', 'br-built-css', 'gzip-built-js', 'br-built-js']);
diff --git a/package.json b/package.json
new file mode 100644
index 00000000..d8015224
--- /dev/null
+++ b/package.json
@@ -0,0 +1,17 @@
+{
+  "name": "jbuk-frontend",
+  "version": "0.0.1",
+  "repository": "https://github.com/jonnybarnes/jbl5",
+  "license": "CC0-1.0",
+  "devDependencies": {
+    "gulp": "~3.9",
+    "gulp-brotli": "^1.0.1",
+    "gulp-zopfli": "^1.0.0",
+    "laravel-elixir": "^5.0.0"
+  },
+  "private": true,
+  "scripts": {
+    "prod": "gulp --production",
+    "dev": "gulp watch"
+  }
+}
diff --git a/phpunit.xml b/phpunit.xml
new file mode 100644
index 00000000..3e884d17
--- /dev/null
+++ b/phpunit.xml
@@ -0,0 +1,30 @@
+
+
+    
+        
+            ./tests
+        
+    
+    
+        
+            ./app
+            
+                ./app/Http/routes.php
+            
+        
+    
+    
+        
+        
+        
+        
+    
+
diff --git a/public/.htaccess b/public/.htaccess
new file mode 100644
index 00000000..903f6392
--- /dev/null
+++ b/public/.htaccess
@@ -0,0 +1,20 @@
+
+    
+        Options -MultiViews
+    
+
+    RewriteEngine On
+
+    # Redirect Trailing Slashes If Not A Folder...
+    RewriteCond %{REQUEST_FILENAME} !-d
+    RewriteRule ^(.*)/$ /$1 [L,R=301]
+
+    # Handle Front Controller...
+    RewriteCond %{REQUEST_FILENAME} !-d
+    RewriteCond %{REQUEST_FILENAME} !-f
+    RewriteRule ^ index.php [L]
+
+    # Handle Authorization Header
+    RewriteCond %{HTTP:Authorization} .
+    RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
+
diff --git a/public/assets/css/alertify.css b/public/assets/css/alertify.css
new file mode 100644
index 00000000..ced38bdd
--- /dev/null
+++ b/public/assets/css/alertify.css
@@ -0,0 +1 @@
+.alertify-logs>*{padding:12px 24px;color:#fff;box-shadow:0 2px 5px 0 rgba(0,0,0,.2);border-radius:1px}.alertify-logs>*,.alertify-logs>.default{background:rgba(0,0,0,.8)}.alertify-logs>.error{background:rgba(244,67,54,.8)}.alertify-logs>.success{background:rgba(76,175,80,.9)}.alertify{position:fixed;background-color:rgba(0,0,0,.3);left:0;right:0;top:0;bottom:0;width:100%;height:100%;z-index:2}.alertify.hide{opacity:0;pointer-events:none}.alertify,.alertify.show{box-sizing:border-box;transition:all .33s cubic-bezier(.25,.8,.25,1)}.alertify,.alertify *{box-sizing:border-box}.alertify .dialog{padding:12px}.alertify .alert,.alertify .dialog{width:100%;margin:0 auto;position:relative;top:50%;-webkit-transform:translateY(-50%);transform:translateY(-50%)}.alertify .alert>*,.alertify .dialog>*{width:400px;max-width:95%;margin:0 auto;text-align:center;padding:12px;background:#fff;box-shadow:0 2px 4px -1px rgba(0,0,0,.14),0 4px 5px 0 rgba(0,0,0,.098),0 1px 10px 0 rgba(0,0,0,.084)}.alertify .alert .msg,.alertify .dialog .msg{padding:12px;margin-bottom:12px;margin:0;text-align:left}.alertify .alert input:not(.form-control),.alertify .dialog input:not(.form-control){margin-bottom:15px;width:100%;font-size:100%;padding:12px}.alertify .alert input:not(.form-control):focus,.alertify .dialog input:not(.form-control):focus{outline-offset:-2px}.alertify .alert nav,.alertify .dialog nav{text-align:right}.alertify .alert nav button:not(.btn):not(.pure-button):not(.md-button):not(.mdl-button),.alertify .dialog nav button:not(.btn):not(.pure-button):not(.md-button):not(.mdl-button){background:transparent;box-sizing:border-box;color:rgba(0,0,0,.87);position:relative;outline:0;border:0;display:inline-block;-webkit-align-items:center;-ms-flex-align:center;-ms-grid-row-align:center;align-items:center;padding:0 6px;margin:6px 8px;line-height:36px;min-height:36px;white-space:nowrap;min-width:88px;text-align:center;text-transform:uppercase;font-size:14px;text-decoration:none;cursor:pointer;border:1px solid transparent;border-radius:2px}.alertify .alert nav button:not(.btn):not(.pure-button):not(.md-button):not(.mdl-button):active,.alertify .alert nav button:not(.btn):not(.pure-button):not(.md-button):not(.mdl-button):hover,.alertify .dialog nav button:not(.btn):not(.pure-button):not(.md-button):not(.mdl-button):active,.alertify .dialog nav button:not(.btn):not(.pure-button):not(.md-button):not(.mdl-button):hover{background-color:rgba(0,0,0,.05)}.alertify .alert nav button:not(.btn):not(.pure-button):not(.md-button):not(.mdl-button):focus,.alertify .dialog nav button:not(.btn):not(.pure-button):not(.md-button):not(.mdl-button):focus{border:1px solid rgba(0,0,0,.1)}.alertify .alert nav button.btn,.alertify .dialog nav button.btn{margin:6px 4px}.alertify-logs{position:fixed;z-index:1}.alertify-logs.bottom,.alertify-logs:not(.top){bottom:16px}.alertify-logs.left,.alertify-logs:not(.right){left:16px}.alertify-logs.left>*,.alertify-logs:not(.right)>*{float:left;-webkit-transform:translateZ(0);transform:translateZ(0);height:auto}.alertify-logs.left>.show,.alertify-logs:not(.right)>.show{left:0}.alertify-logs.left>*,.alertify-logs.left>.hide,.alertify-logs:not(.right)>*,.alertify-logs:not(.right)>.hide{left:-110%}.alertify-logs.right{right:16px}.alertify-logs.right>*{float:right;-webkit-transform:translateZ(0);transform:translateZ(0)}.alertify-logs.right>.show{right:0;opacity:1}.alertify-logs.right>*,.alertify-logs.right>.hide{right:-110%;opacity:0}.alertify-logs.top{top:0}.alertify-logs>*{box-sizing:border-box;transition:all .4s cubic-bezier(.25,.8,.25,1);position:relative;clear:both;-webkit-backface-visibility:hidden;backface-visibility:hidden;-webkit-perspective:1000;perspective:1000;max-height:0;margin:0;padding:0;overflow:hidden;opacity:0;pointer-events:none}.alertify-logs>.show{margin-top:12px;opacity:1;max-height:1000px;padding:12px;pointer-events:auto}
\ No newline at end of file
diff --git a/public/assets/css/global.css b/public/assets/css/global.css
new file mode 100644
index 00000000..1750d873
--- /dev/null
+++ b/public/assets/css/global.css
@@ -0,0 +1,243 @@
+html {
+  background: url("/assets/img/escheresque.png"); }
+
+.map {
+  height: 150px; }
+
+html {
+  box-sizing: border-box; }
+
+*, *:before, *:after {
+  box-sizing: inherit; }
+
+#topheader {
+  display: -webkit-flex;
+  display: -ms-flexbox;
+  display: flex;
+  -webkit-flex-flow: row;
+  -ms-flex-flow: row;
+  flex-flow: row; }
+
+#topheader a {
+  padding: 0.5em 1em; }
+
+nav {
+  padding-top: 0.5em; }
+
+.social-list {
+  padding-left: 2em; }
+
+.note {
+  background-color: #eee8d5;
+  box-shadow: 0 0 10px 2px #93a1a1;
+  padding: 0.5em 0.5em;
+  margin-top: 1em; }
+
+.note:after {
+  content: " ";
+  display: block;
+  height: 0;
+  clear: both; }
+
+.note a {
+  word-wrap: break-word; }
+
+.note .e-content p:first-child {
+  margin-top: 0; }
+
+.note-metadata {
+  width: 100%; }
+
+.social-links {
+  float: right; }
+
+.social-links a {
+  text-decoration: none; }
+
+.icon {
+  width: auto;
+  height: 1em;
+  fill: #268bd2; }
+
+.reply {
+  margin-left: 2em;
+  margin-right: 2em;
+  font-size: 0.8em;
+  padding: 0.5em 0.5em; }
+
+.reply-to {
+  margin-left: 2em;
+  margin-right: 2em;
+  font-size: 0.8em;
+  padding-top: 2em; }
+
+.reply-to + .note {
+  margin-top: 0.3em; }
+
+.mini-h-card {
+  border-radius: 2px;
+  border: 1px solid #586e75;
+  padding: 0 0.2em;
+  text-decoration: none;
+  margin-right: 5px;
+  white-space: nowrap; }
+
+.mini-h-card img {
+  height: 1.26em;
+  display: inline;
+  border-radius: 2px;
+  vertical-align: text-bottom; }
+
+.like-photo {
+  height: 1.26em; }
+
+.reply .e-content {
+  margin-top: 0.5em;
+  padding-left: 0.5em; }
+
+.notes-subtitle {
+  font-size: 1em; }
+
+.note-photo {
+  width: 100%;
+  height: auto;
+  image-orientation: from-image; }
+
+article header {
+  margin-top: 0.5em;
+  margin-bottom: 0.8em; }
+
+.post-info {
+  font-size: 0.8em;
+  font-style: italic;
+  margin-top: -0.8em; }
+
+.contact {
+  position: relative; }
+
+.contact-links {
+  list-style-type: none; }
+
+.contact img {
+  height: auto;
+  width: 2em;
+  position: absolute;
+  top: 0;
+  left: 0; }
+
+.contact-info {
+  margin-left: 2em; }
+
+#map {
+  height: 300px; }
+
+/* media queries */
+@media (min-width: 700px) {
+  main {
+    margin-left: 10em;
+    margin-right: 10em; }
+  footer {
+    margin-left: 13em;
+    margin-right: 13em; }
+  .youtube {
+    width: 640px;
+    height: 360px; } }
+
+@media (max-width: 699px) {
+  main {
+    margin-left: 10px;
+    margin-right: 10px; }
+  article {
+    word-wrap: break-word; }
+  footer {
+    margin-left: 15px;
+    margin-right: 15px; }
+  .youtube {
+    width: 100%;
+    height: auto; } }
+
+body {
+  text-rendering: optimizeLegibility;
+  -webkit-font-feature-settings: "liga";
+  font-feature-settings: "liga";
+  font-family: "leitura-news", serif;
+  font-size: 1.2em; }
+
+#topheader h1 {
+  font-family: "leitura-news", serif; }
+
+h1 {
+  font-family: "prenton", sans-serif; }
+
+#topheader a {
+  text-decoration: none; }
+
+nav {
+  -webkit-font-feature-settings: "dlig";
+  font-feature-settings: "dlig"; }
+
+article header h1 a {
+  text-decoration: none; }
+
+article div a {
+  text-decoration: none; }
+
+footer {
+  font-size: 0.8em; }
+
+.emoji {
+  width: auto;
+  height: 1em; }
+
+body {
+  color: #002b36; }
+
+header a {
+  color: #002b36; }
+
+a {
+  color: #268bd2; }
+
+form {
+  width: 100%; }
+
+fieldset {
+  min-width: 0;
+  width: 100%; }
+
+input[type="text"], input[type="file"], textarea {
+  width: 100%; }
+
+input, button, textarea {
+  -webkit-appearance: none;
+  -moz-appearance: none;
+  background-color: #002b36;
+  color: #fdf6e3;
+  border: 1px solid #fdf6e3;
+  border-radius: 4px; }
+
+button:hover {
+  transition: 0.5s ease-in-out;
+  background-color: #fdf6e3;
+  color: #002b36; }
+
+button:disabled {
+  background-color: #93a1a1;
+  color: #002b36; }
+
+input[type="checkbox"] {
+  -webkit-appearance: checkbox;
+  -moz-appearance: checkbox; }
+
+#photo {
+  background: inherit;
+  color: inherit;
+  border: none; }
+
+.twitter-tweet-rendered {
+  margin-bottom: 0 !important; }
+
+.twitter-tweet-rendered + .note {
+  margin-top: 0; }
+
+/*# sourceMappingURL=global.css.map */
diff --git a/public/assets/css/global.css.map b/public/assets/css/global.css.map
new file mode 100644
index 00000000..fa39372b
--- /dev/null
+++ b/public/assets/css/global.css.map
@@ -0,0 +1 @@
+{"version":3,"sources":["global.scss","layout.scss","components/fonts.scss","components/colours.scss","components/forms.scss","components/twitter.scss"],"names":[],"mappings":"AAyBA;EACC,+CAAe,EACf;;AAED;EACC,cAAc,EACd;;AC5BD;EACC,uBAAuB,EACvB;;AAED;EACC,oBAAoB,EACpB;;AAED;EACC,sBAAc;EAAd,qBAAc;EAAd,cAAc;EACd,uBAAe;EAAf,mBAAe;EAAf,eAAe,EACf;;AAED;EACC,mBAAmB,EACnB;;AAED;EACC,mBAAmB,EACnB;;AAED;EACC,kBAAkB,EAClB;;AAED;EACC,0BDhBkB;ECiBlB,iCDlBkB;ECmBlB,qBAAqB;EACrB,gBAAgB,EAChB;;AAED;EACC,aAAa;EACb,eAAe;EACf,UAAU;EACV,YAAY,EACZ;;AAED;EACC,sBAAsB,EACtB;;AAED;EACC,cAAc,EACd;;AAED;EACC,YAAY,EACZ;;AAED;EACC,aAAa,EACb;;AAED;EACC,sBAAsB,EACtB;;AAED;EACC,YAAY;EACZ,YAAY;EACZ,cD7CkB,EC8ClB;;AAED;EACC,iBAAiB;EACjB,kBAAkB;EAClB,iBAAiB;EACjB,qBAAqB,EACrB;;AAED;EACC,iBAAiB;EACjB,kBAAkB;EAClB,iBAAiB;EACjB,iBAAiB,EACjB;;AAED;EACC,kBAAkB,EAClB;;AAED;EACC,mBAAmB;EACnB,0BD/EkB;ECgFlB,iBAAiB;EACjB,sBAAsB;EACtB,kBAAkB;EAClB,oBAAoB,EACpB;;AAED;EACC,eAAe;EACf,gBAAgB;EAChB,mBAAmB;EACnB,4BAA4B,EAC5B;;AAED;EACC,eAAe,EACf;;AAED;EACC,kBAAkB;EAClB,oBAAoB,EACpB;;AAED;EACC,eAAe,EACf;;AAED;EACC,YAAY;EACZ,aAAa;EACb,8BAA8B,EAC9B;;AAID;EACC,kBAAkB;EAClB,qBAAqB,EACrB;;AAED;EACC,iBAAiB;EACjB,mBAAmB;EACnB,mBAAmB,EACnB;;AAGD;EACC,mBAAmB,EACnB;;AAED;EACC,sBAAsB,EACtB;;AAED;EACC,aAAa;EACb,WAAW;EACX,mBAAmB;EACnB,OAAO;EACP,QAAQ,EACR;;AAED;EACC,iBAAiB,EACjB;;AAED;EACC,cAAc,EACd;;AAED,mBAAmB;AACnB;EACC;IACC,kBAAkB;IAClB,mBAAmB,EACnB;EAED;IACC,kBAAkB;IAClB,mBAAmB,EACnB;EAED;IACC,aAAa;IACb,cAAc,EACd,EAAA;;AAGF;EACC;IACC,kBAAkB;IAClB,mBAAmB,EACnB;EAED;IACC,sBAAsB,EACtB;EAED;IACC,kBAAkB;IAClB,mBAAmB,EACnB;EAED;IACC,YAAY;IACZ,aAAa,EACb,EAAA;;ACjMF;EACC,mCAAmC;EACnC,sCAA8B;EAA9B,8BAA8B;EAC9B,mCFFsC;EEGtC,iBAAiB,EACjB;;AAED;EACC,mCFPsC,EEQtC;;AAED;EACC,mCFVyC,EEWzC;;AAED;EACC,sBAAsB,EACtB;;AAED;EACC,sCAA8B;EAA9B,8BAA8B,EAC9B;;AAED;EACC,sBAAsB,EACtB;;AAED;EACC,sBAAsB,EACtB;;AAED;EACC,iBAAiB,EACjB;;AAED;EACC,YAAY;EACZ,YAAY,EACZ;;ACvCD;EACC,eHKkB,EGJlB;;AAED;EACC,eHCkB,EGAlB;;AAED;EACC,eHUkB,EGTlB;;ACTD;EACC,YAAY,EACZ;;AAED;EACC,aAAa;EACb,YAAY,EACZ;;AAED;EACC,YAAY,EACZ;;AAED;EACC,yBAAyB;EACzB,sBAAsB;EACtB,0BJXkB;EIYlB,eJLkB;EIMlB,0BJNkB;EIOlB,mBAAmB,EACnB;;AAED;EACC,6BAA6B;EAC7B,0BJZkB;EIalB,eJpBkB,EIqBlB;;AAED;EACC,0BJnBkB;EIoBlB,eJzBkB,EI0BlB;;AAED;EACC,6BAA6B;EAC7B,0BAA0B,EAC1B;;AAED;EACC,oBAAoB;EACpB,eAAe;EACf,aAAa,EACb;;AC1CD;EACE,4BAA4B,EAC7B;;AAED;EACE,cAAc,EACf","file":"global.css","sourcesContent":["//global.scss\n\n//variables\n$font-stack-body: \"leitura-news\", serif;\n$font-stack-headers: \"prenton\", sans-serif;\n\n//solarized variables TERMCOL\n$base03:    #002b36;//brblack\n$base02:    #073642;//black\n$base01:    #586e75;//brgreen\n$base00:    #657b83;//bryellow\n$base0:     #839496;//brblue\n$base1:     #93a1a1;//brcyan\n$base2:     #eee8d5;//white\n$base3:     #fdf6e3;//brwhite\n$yellow:    #b58900;\n$orange:    #cb4b16;\n$red:       #dc322f;\n$magenta:   #d33682;\n$violet:    #6c71c4;\n$blue:      #268bd2;\n$cyan:      #2aa198;\n$green:     #859900;\n\n//global styles\nhtml {\n\tbackground: url('/assets/img/escheresque.png');\n}\n\n.map {\n\theight: 150px;\n}\n\n//layout\n@import \"layout\";\n\n//components\n@import \"components/fonts\";\n@import \"components/colours\";\n@import \"components/forms\";\n@import \"components/twitter\";\n","//layout.scss\n\n//boxes\nhtml {\n\tbox-sizing: border-box;\n}\n\n*, *:before, *:after {\n\tbox-sizing: inherit;\n}\n\n#topheader {\n\tdisplay: flex;\n\tflex-flow: row;\n}\n\n#topheader a {\n\tpadding: 0.5em 1em;\n}\n\nnav {\n\tpadding-top: 0.5em;\n}\n\n.social-list {\n\tpadding-left: 2em;\n}\n\n.note {\n\tbackground-color: $base2;\n\tbox-shadow: 0 0 10px 2px $base1;\n\tpadding: 0.5em 0.5em;\n\tmargin-top: 1em;\n}\n\n.note:after {\n\tcontent: \" \";\n\tdisplay: block;\n\theight: 0;\n\tclear: both;\n}\n\n.note a {\n\tword-wrap: break-word;\n}\n\n.note .e-content p:first-child {\n\tmargin-top: 0;\n}\n\n.note-metadata {\n\twidth: 100%;\n}\n\n.social-links {\n\tfloat: right;\n}\n\n.social-links a {\n\ttext-decoration: none;\n}\n\n.icon {\n\twidth: auto;\n\theight: 1em;\n\tfill: $blue;\n}\n\n.reply {\n\tmargin-left: 2em;\n\tmargin-right: 2em;\n\tfont-size: 0.8em;\n\tpadding: 0.5em 0.5em;\n}\n\n.reply-to {\n\tmargin-left: 2em;\n\tmargin-right: 2em;\n\tfont-size: 0.8em;\n\tpadding-top: 2em;\n}\n\n.reply-to + .note {\n\tmargin-top: 0.3em;\n}\n\n.mini-h-card {\n\tborder-radius: 2px;\n\tborder: 1px solid $base01;\n\tpadding: 0 0.2em;\n\ttext-decoration: none;\n\tmargin-right: 5px;\n\twhite-space: nowrap;\n}\n\n.mini-h-card img {\n\theight: 1.26em;\n\tdisplay: inline;\n\tborder-radius: 2px;\n\tvertical-align: text-bottom;\n}\n\n.like-photo {\n\theight: 1.26em;\n}\n\n.reply .e-content {\n\tmargin-top: 0.5em;\n\tpadding-left: 0.5em;\n}\n\n.notes-subtitle {\n\tfont-size: 1em;\n}\n\n.note-photo {\n\twidth: 100%;\n\theight: auto;\n\timage-orientation: from-image;\n}\n\n//articles\n\narticle header {\n\tmargin-top: 0.5em;\n\tmargin-bottom: 0.8em;\n}\n\n.post-info {\n\tfont-size: 0.8em;\n\tfont-style: italic;\n\tmargin-top: -0.8em;\n}\n\n//contacts\n.contact {\n\tposition: relative;\n}\n\n.contact-links {\n\tlist-style-type: none;\n}\n\n.contact img {\n\theight: auto;\n\twidth: 2em;\n\tposition: absolute;\n\ttop: 0;\n\tleft: 0;\n}\n\n.contact-info {\n\tmargin-left: 2em;\n}\n\n#map {\n\theight: 300px;\n}\n\n/* media queries */\n@media (min-width: 700px) {\n\tmain {\n\t\tmargin-left: 10em;\n\t\tmargin-right: 10em;\n\t}\n\n\tfooter {\n\t\tmargin-left: 13em;\n\t\tmargin-right: 13em;\n\t}\n\n\t.youtube {\n\t\twidth: 640px;\n\t\theight: 360px;\n\t}\n}\n\n@media (max-width: 699px) {\n\tmain {\n\t\tmargin-left: 10px;\n\t\tmargin-right: 10px;\n\t}\n\n\tarticle {\n\t\tword-wrap: break-word;\n\t}\n\n\tfooter {\n\t\tmargin-left: 15px;\n\t\tmargin-right: 15px;\n\t}\n\n\t.youtube {\n\t\twidth: 100%;\n\t\theight: auto;\n\t}\n}\n","//fonts.scss\n\nbody {\n\ttext-rendering: optimizeLegibility;\n\tfont-feature-settings: \"liga\";\n\tfont-family: $font-stack-body;\n\tfont-size: 1.2em;\n}\n\n#topheader h1 {\n\tfont-family: $font-stack-body;\n}\n\nh1 {\n\tfont-family: $font-stack-headers;\n}\n\n#topheader a {\n\ttext-decoration: none;\n}\n\nnav {\n\tfont-feature-settings: \"dlig\";\n}\n\narticle header h1 a {\n\ttext-decoration: none;\n}\n\narticle div a {\n\ttext-decoration: none;\n}\n\nfooter {\n\tfont-size: 0.8em;\n}\n\n.emoji {\n\twidth: auto;\n\theight: 1em;\n}\n","//colours.scss\nbody {\n\tcolor: $base03;\n}\n\nheader a {\n\tcolor: $base03;\n}\n\na {\n\tcolor: $blue;\n}","//forms.scss\n\nform {\n\twidth: 100%;\n}\n\nfieldset {\n\tmin-width: 0;\n\twidth: 100%;\n}\n\ninput[type=\"text\"], input[type=\"file\"], textarea {\n\twidth: 100%;\n}\n\ninput, button, textarea {\n\t-webkit-appearance: none;\n\t-moz-appearance: none;\n\tbackground-color: $base03;\n\tcolor: $base3;\n\tborder: 1px solid $base3;\n\tborder-radius: 4px;\n}\n\nbutton:hover {\n\ttransition: 0.5s ease-in-out;\n\tbackground-color: $base3;\n\tcolor: $base03;\n}\n\nbutton:disabled {\n\tbackground-color: $base1;\n\tcolor: $base03;\n}\n\ninput[type=\"checkbox\"] {\n\t-webkit-appearance: checkbox;\n\t-moz-appearance: checkbox;\n}\n\n#photo {\n\tbackground: inherit;\n\tcolor: inherit;\n\tborder: none;\n}\n","//twitter.scss\n\n.twitter-tweet-rendered {\n  margin-bottom: 0 !important;\n}\n\n.twitter-tweet-rendered + .note {\n  margin-top: 0;\n}\n"],"sourceRoot":"/source/"}
\ No newline at end of file
diff --git a/public/assets/css/images/icons-000000@2x.png b/public/assets/css/images/icons-000000@2x.png
new file mode 100644
index 00000000..d65438c1
Binary files /dev/null and b/public/assets/css/images/icons-000000@2x.png differ
diff --git a/public/assets/css/normalize.css b/public/assets/css/normalize.css
new file mode 100644
index 00000000..5e5e3c89
--- /dev/null
+++ b/public/assets/css/normalize.css
@@ -0,0 +1,424 @@
+/*! normalize.css v3.0.3 | MIT License | github.com/necolas/normalize.css */
+
+/**
+ * 1. Set default font family to sans-serif.
+ * 2. Prevent iOS and IE text size adjust after device orientation change,
+ *    without disabling user zoom.
+ */
+
+html {
+  font-family: sans-serif; /* 1 */
+  -ms-text-size-adjust: 100%; /* 2 */
+  -webkit-text-size-adjust: 100%; /* 2 */
+}
+
+/**
+ * Remove default margin.
+ */
+
+body {
+  margin: 0;
+}
+
+/* HTML5 display definitions
+   ========================================================================== */
+
+/**
+ * Correct `block` display not defined for any HTML5 element in IE 8/9.
+ * Correct `block` display not defined for `details` or `summary` in IE 10/11
+ * and Firefox.
+ * Correct `block` display not defined for `main` in IE 11.
+ */
+
+article,
+aside,
+details,
+figcaption,
+figure,
+footer,
+header,
+hgroup,
+main,
+menu,
+nav,
+section,
+summary {
+  display: block;
+}
+
+/**
+ * 1. Correct `inline-block` display not defined in IE 8/9.
+ * 2. Normalize vertical alignment of `progress` in Chrome, Firefox, and Opera.
+ */
+
+audio,
+canvas,
+progress,
+video {
+  display: inline-block; /* 1 */
+  vertical-align: baseline; /* 2 */
+}
+
+/**
+ * Prevent modern browsers from displaying `audio` without controls.
+ * Remove excess height in iOS 5 devices.
+ */
+
+audio:not([controls]) {
+  display: none;
+  height: 0;
+}
+
+/**
+ * Address `[hidden]` styling not present in IE 8/9/10.
+ * Hide the `template` element in IE 8/9/10/11, Safari, and Firefox < 22.
+ */
+
+[hidden],
+template {
+  display: none;
+}
+
+/* Links
+   ========================================================================== */
+
+/**
+ * Remove the gray background color from active links in IE 10.
+ */
+
+a {
+  background-color: transparent;
+}
+
+/**
+ * Improve readability of focused elements when they are also in an
+ * active/hover state.
+ */
+
+a:active,
+a:hover {
+  outline: 0;
+}
+
+/* Text-level semantics
+   ========================================================================== */
+
+/**
+ * Address styling not present in IE 8/9/10/11, Safari, and Chrome.
+ */
+
+abbr[title] {
+  border-bottom: 1px dotted;
+}
+
+/**
+ * Address style set to `bolder` in Firefox 4+, Safari, and Chrome.
+ */
+
+b,
+strong {
+  font-weight: bold;
+}
+
+/**
+ * Address styling not present in Safari and Chrome.
+ */
+
+dfn {
+  font-style: italic;
+}
+
+/**
+ * Address variable `h1` font-size and margin within `section` and `article`
+ * contexts in Firefox 4+, Safari, and Chrome.
+ */
+
+h1 {
+  font-size: 2em;
+  margin: 0.67em 0;
+}
+
+/**
+ * Address styling not present in IE 8/9.
+ */
+
+mark {
+  background: #ff0;
+  color: #000;
+}
+
+/**
+ * Address inconsistent and variable font size in all browsers.
+ */
+
+small {
+  font-size: 80%;
+}
+
+/**
+ * Prevent `sub` and `sup` affecting `line-height` in all browsers.
+ */
+
+sub,
+sup {
+  font-size: 75%;
+  line-height: 0;
+  position: relative;
+  vertical-align: baseline;
+}
+
+sup {
+  top: -0.5em;
+}
+
+sub {
+  bottom: -0.25em;
+}
+
+/* Embedded content
+   ========================================================================== */
+
+/**
+ * Remove border when inside `a` element in IE 8/9/10.
+ */
+
+img {
+  border: 0;
+}
+
+/**
+ * Correct overflow not hidden in IE 9/10/11.
+ */
+
+svg:not(:root) {
+  overflow: hidden;
+}
+
+/* Grouping content
+   ========================================================================== */
+
+/**
+ * Address margin not present in IE 8/9 and Safari.
+ */
+
+figure {
+  margin: 1em 40px;
+}
+
+/**
+ * Address differences between Firefox and other browsers.
+ */
+
+hr {
+  box-sizing: content-box;
+  height: 0;
+}
+
+/**
+ * Contain overflow in all browsers.
+ */
+
+pre {
+  overflow: auto;
+}
+
+/**
+ * Address odd `em`-unit font size rendering in all browsers.
+ */
+
+code,
+kbd,
+pre,
+samp {
+  font-family: monospace, monospace;
+  font-size: 1em;
+}
+
+/* Forms
+   ========================================================================== */
+
+/**
+ * Known limitation: by default, Chrome and Safari on OS X allow very limited
+ * styling of `select`, unless a `border` property is set.
+ */
+
+/**
+ * 1. Correct color not being inherited.
+ *    Known issue: affects color of disabled elements.
+ * 2. Correct font properties not being inherited.
+ * 3. Address margins set differently in Firefox 4+, Safari, and Chrome.
+ */
+
+button,
+input,
+optgroup,
+select,
+textarea {
+  color: inherit; /* 1 */
+  font: inherit; /* 2 */
+  margin: 0; /* 3 */
+}
+
+/**
+ * Address `overflow` set to `hidden` in IE 8/9/10/11.
+ */
+
+button {
+  overflow: visible;
+}
+
+/**
+ * Address inconsistent `text-transform` inheritance for `button` and `select`.
+ * All other form control elements do not inherit `text-transform` values.
+ * Correct `button` style inheritance in Firefox, IE 8/9/10/11, and Opera.
+ * Correct `select` style inheritance in Firefox.
+ */
+
+button,
+select {
+  text-transform: none;
+}
+
+/**
+ * 1. Avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio`
+ *    and `video` controls.
+ * 2. Correct inability to style clickable `input` types in iOS.
+ * 3. Improve usability and consistency of cursor style between image-type
+ *    `input` and others.
+ */
+
+button,
+html input[type="button"], /* 1 */
+input[type="reset"],
+input[type="submit"] {
+  -webkit-appearance: button; /* 2 */
+  cursor: pointer; /* 3 */
+}
+
+/**
+ * Re-set default cursor for disabled elements.
+ */
+
+button[disabled],
+html input[disabled] {
+  cursor: default;
+}
+
+/**
+ * Remove inner padding and border in Firefox 4+.
+ */
+
+button::-moz-focus-inner,
+input::-moz-focus-inner {
+  border: 0;
+  padding: 0;
+}
+
+/**
+ * Address Firefox 4+ setting `line-height` on `input` using `!important` in
+ * the UA stylesheet.
+ */
+
+input {
+  line-height: normal;
+}
+
+/**
+ * It's recommended that you don't attempt to style these elements.
+ * Firefox's implementation doesn't respect box-sizing, padding, or width.
+ *
+ * 1. Address box sizing set to `content-box` in IE 8/9/10.
+ * 2. Remove excess padding in IE 8/9/10.
+ */
+
+input[type="checkbox"],
+input[type="radio"] {
+  box-sizing: border-box; /* 1 */
+  padding: 0; /* 2 */
+}
+
+/**
+ * Fix the cursor style for Chrome's increment/decrement buttons. For certain
+ * `font-size` values of the `input`, it causes the cursor style of the
+ * decrement button to change from `default` to `text`.
+ */
+
+input[type="number"]::-webkit-inner-spin-button,
+input[type="number"]::-webkit-outer-spin-button {
+  height: auto;
+}
+
+/**
+ * 1. Address `appearance` set to `searchfield` in Safari and Chrome.
+ * 2. Address `box-sizing` set to `border-box` in Safari and Chrome.
+ */
+
+input[type="search"] {
+  -webkit-appearance: textfield; /* 1 */
+  box-sizing: content-box; /* 2 */
+}
+
+/**
+ * Remove inner padding and search cancel button in Safari and Chrome on OS X.
+ * Safari (but not Chrome) clips the cancel button when the search input has
+ * padding (and `textfield` appearance).
+ */
+
+input[type="search"]::-webkit-search-cancel-button,
+input[type="search"]::-webkit-search-decoration {
+  -webkit-appearance: none;
+}
+
+/**
+ * Define consistent border, margin, and padding.
+ */
+
+fieldset {
+  border: 1px solid #c0c0c0;
+  margin: 0 2px;
+  padding: 0.35em 0.625em 0.75em;
+}
+
+/**
+ * 1. Correct `color` not being inherited in IE 8/9/10/11.
+ * 2. Remove padding so people aren't caught out if they zero out fieldsets.
+ */
+
+legend {
+  border: 0; /* 1 */
+  padding: 0; /* 2 */
+}
+
+/**
+ * Remove default vertical scrollbar in IE 8/9/10/11.
+ */
+
+textarea {
+  overflow: auto;
+}
+
+/**
+ * Don't inherit the `font-weight` (applied by a rule above).
+ * NOTE: the default cannot safely be changed in Chrome and Safari on OS X.
+ */
+
+optgroup {
+  font-weight: bold;
+}
+
+/* Tables
+   ========================================================================== */
+
+/**
+ * Remove most spacing between table cells.
+ */
+
+table {
+  border-collapse: collapse;
+  border-spacing: 0;
+}
+
+td,
+th {
+  padding: 0;
+}
diff --git a/public/assets/css/prism.css b/public/assets/css/prism.css
new file mode 100644
index 00000000..86122e46
--- /dev/null
+++ b/public/assets/css/prism.css
@@ -0,0 +1,188 @@
+/* http://prismjs.com/download.html?themes=prism-dark&languages=markup+css+clike+javascript+git+http+markdown+php+php-extras+scss+sql&plugins=line-numbers+show-invisibles */
+/**
+ * prism.js Dark theme for JavaScript, CSS and HTML
+ * Based on the slides of the talk “/Reg(exp){2}lained/”
+ * @author Lea Verou
+ */
+
+code[class*="language-"],
+pre[class*="language-"] {
+        color: white;
+        text-shadow: 0 -.1em .2em black;
+        font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace;
+        direction: ltr;
+        text-align: left;
+        white-space: pre;
+        word-spacing: normal;
+        word-break: normal;
+        line-height: 1.5;
+
+        -moz-tab-size: 4;
+        -o-tab-size: 4;
+        tab-size: 4;
+
+        -webkit-hyphens: none;
+        -moz-hyphens: none;
+        -ms-hyphens: none;
+        hyphens: none;
+}
+
+@media print {
+        code[class*="language-"],
+        pre[class*="language-"] {
+                text-shadow: none;
+        }
+}
+
+pre[class*="language-"],
+:not(pre) > code[class*="language-"] {
+        background: hsl(30, 20%, 25%);
+}
+
+/* Code blocks */
+pre[class*="language-"] {
+        padding: 1em;
+        margin: .5em 0;
+        overflow: auto;
+        border: .3em solid hsl(30, 20%, 40%);
+        border-radius: .5em;
+        box-shadow: 1px 1px .5em black inset;
+}
+
+/* Inline code */
+:not(pre) > code[class*="language-"] {
+        padding: .15em .2em .05em;
+        border-radius: .3em;
+        border: .13em solid hsl(30, 20%, 40%);
+        box-shadow: 1px 1px .3em -.1em black inset;
+}
+
+.token.comment,
+.token.prolog,
+.token.doctype,
+.token.cdata {
+        color: hsl(30, 20%, 50%);
+}
+
+.token.punctuation {
+        opacity: .7;
+}
+
+.namespace {
+        opacity: .7;
+}
+
+.token.property,
+.token.tag,
+.token.boolean,
+.token.number,
+.token.constant,
+.token.symbol {
+        color: hsl(350, 40%, 70%);
+}
+
+.token.selector,
+.token.attr-name,
+.token.string,
+.token.char,
+.token.builtin,
+.token.inserted {
+        color: hsl(75, 70%, 60%);
+}
+
+.token.operator,
+.token.entity,
+.token.url,
+.language-css .token.string,
+.style .token.string,
+.token.variable {
+        color: hsl(40, 90%, 60%);
+}
+
+.token.atrule,
+.token.attr-value,
+.token.keyword {
+        color: hsl(350, 40%, 70%);
+}
+
+.token.regex,
+.token.important {
+        color: #e90;
+}
+
+.token.important,
+.token.bold {
+        font-weight: bold;
+}
+.token.italic {
+        font-style: italic;
+}
+
+.token.entity {
+        cursor: help;
+}
+
+.token.deleted {
+        color: red;
+}
+
+pre.line-numbers {
+        position: relative;
+        padding-left: 3.8em;
+        counter-reset: linenumber;
+}
+
+pre.line-numbers > code {
+        position: relative;
+}
+
+.line-numbers .line-numbers-rows {
+        position: absolute;
+        pointer-events: none;
+        top: 0;
+        font-size: 100%;
+        left: -3.8em;
+        width: 3em; /* works for line-numbers below 1000 lines */
+        letter-spacing: -1px;
+        border-right: 1px solid #999;
+
+        -webkit-user-select: none;
+        -moz-user-select: none;
+        -ms-user-select: none;
+        user-select: none;
+
+}
+
+        .line-numbers-rows > span {
+                pointer-events: none;
+                display: block;
+                counter-increment: linenumber;
+        }
+
+                .line-numbers-rows > span:before {
+                        content: counter(linenumber);
+                        color: #999;
+                        display: block;
+                        padding-right: 0.8em;
+                        text-align: right;
+                }
+.token.tab:not(:empty):before,
+.token.cr:before,
+.token.lf:before {
+        color: hsl(24, 20%, 85%);
+}
+
+.token.tab:not(:empty):before {
+    content: '\21E5';
+}
+
+.token.cr:before {
+    content: '\240D';
+}
+
+.token.crlf:before {
+    content: '\240D\240A';
+}
+.token.lf:before {
+    content: '\240A';
+}
diff --git a/public/assets/css/projects.css b/public/assets/css/projects.css
new file mode 100644
index 00000000..d108175a
--- /dev/null
+++ b/public/assets/css/projects.css
@@ -0,0 +1,10 @@
+#projects {
+	padding-left: 33.33%;
+}
+
+h3 {
+	float: left;
+	width: 45%;
+	margin: 0 5% 0 -50%;
+	text-align: right;
+}
diff --git a/public/assets/css/sanitize.min.css b/public/assets/css/sanitize.min.css
new file mode 100644
index 00000000..9d0d9800
--- /dev/null
+++ b/public/assets/css/sanitize.min.css
@@ -0,0 +1,2 @@
+/*! sanitize.css v3.2.0 | CC0 1.0 Public Domain | github.com/10up/sanitize.css */audio:not([controls]){display:none}button{-webkit-appearance:button;overflow:visible}details{display:block}html{-ms-overflow-style:-ms-autohiding-scrollbar;overflow-y:scroll;-webkit-text-size-adjust:100%}input{-webkit-border-radius:0}input[type=button],input[type=reset],input[type=submit]{-webkit-appearance:button}input[type=number]{width:auto}input[type=search]{-webkit-appearance:textfield}input[type=search]::-webkit-search-cancel-button,input[type=search]::-webkit-search-decoration{-webkit-appearance:none}main{display:block}pre{overflow:auto}progress{display:inline-block}small{font-size:75%}summary{display:block}svg:not(:root){overflow:hidden}template{display:none}textarea{overflow:auto}[hidden]{display:none}*,:after,:before{box-sizing:inherit}*{font-size:inherit;line-height:inherit}:after,:before{text-decoration:inherit;vertical-align:inherit}*,:after,:before{border-style:solid;border-width:0}*{background-repeat:no-repeat;margin:0;padding:0}:root{background-color:#fff;box-sizing:border-box;color:#000;cursor:default;font:100%/1.5 sans-serif}a{text-decoration:none}audio,canvas,iframe,img,svg,video{vertical-align:middle}button,input,select,textarea{background-color:transparent;color:inherit;font-family:inherit;font-style:inherit;font-weight:inherit}[type=button],[type=date],[type=datetime-local],[type=datetime],[type=email],[type=month],[type=number],[type=password],[type=reset],[type=search],[type=submit],[type=tel],[type=text],[type=time],[type=url],[type=week],button,select,textarea{min-height:1.5em}code,kbd,pre,samp{font-family:monospace}nav ol,nav ul{list-style:none}select{-moz-appearance:none;-webkit-appearance:none}select::-ms-expand{display:none}select::-ms-value{color:currentColor}table{border-collapse:collapse;border-spacing:0}textarea{resize:vertical}::-moz-selection{background-color:#b3d4fc;color:#fff;text-shadow:none}::selection{background-color:#b3d4fc;color:#fff;text-shadow:none}[aria-busy=true]{cursor:progress}[aria-controls]{cursor:pointer}[aria-disabled]{cursor:default}[hidden][aria-hidden=false]{clip:rect(0 0 0 0);display:inherit;position:absolute}[hidden][aria-hidden=false]:focus{clip:auto}[tabindex],a,area,button,input,label,select,textarea{-ms-touch-action:manipulation;touch-action:manipulation}
+/*# sourceMappingURL=sanitize.min.css.map */
\ No newline at end of file
diff --git a/public/assets/img/escheresque.png b/public/assets/img/escheresque.png
new file mode 100644
index 00000000..a1a4638a
Binary files /dev/null and b/public/assets/img/escheresque.png differ
diff --git a/public/assets/img/escheresque@2x.png b/public/assets/img/escheresque@2x.png
new file mode 100644
index 00000000..f70cc4c0
Binary files /dev/null and b/public/assets/img/escheresque@2x.png differ
diff --git a/public/assets/img/jmb-bw.png b/public/assets/img/jmb-bw.png
new file mode 100644
index 00000000..9db94a2f
Binary files /dev/null and b/public/assets/img/jmb-bw.png differ
diff --git a/public/assets/img/notes/.gitignore b/public/assets/img/notes/.gitignore
new file mode 100644
index 00000000..2c433a89
--- /dev/null
+++ b/public/assets/img/notes/.gitignore
@@ -0,0 +1,3 @@
+# ignore everthing in here except the .gitignore itself
+*
+!.gitignore
diff --git a/public/assets/jonnybarnes-public-key-ecc.asc b/public/assets/jonnybarnes-public-key-ecc.asc
new file mode 100644
index 00000000..29458716
--- /dev/null
+++ b/public/assets/jonnybarnes-public-key-ecc.asc
@@ -0,0 +1,22 @@
+-----BEGIN PGP PUBLIC KEY BLOCK-----
+
+mHMEVLAKNRMJKyQDAwIIAQELAwMEXwZt3GCBMfYdy4E6SYBlhI5OWB5KLWDJIszb
+rHVk1lFzuCh8v9vO3TMJipved89lKdsqc8xa1rtUhDUSyk3DN6G9EA8CaZzPAI0W
+3rIIjKr/6XdsrCbr4uBm8pfxojlrtCNKb25ueSBCYXJuZXMgPGpvbm55QGpvbm55
+YmFybmVzLnVrPoifBBMTCgAnAhsDBQsJCAcDBRUKCQgLBRYDAgEAAh4BAheABQJW
+jWw8BQkDvpWHAAoJEE6N3A7eXSOmEV0Bf1mwzjzKSZvn5l/VhQTrMbM47K82w/wj
+JYF9ZTy1jmXTWTMB3grnjB+VhkF0fJEO7QF+N6EULNZu9xpUX00LfasRvjhDQEoZ
+GWLJWuES3pIheS2wUKScnVxv/3Ntxl9DZyBRuHcEVLAKNRIJKyQDAwIIAQELAwME
+MyNUNRiVoPuUZHkZmoR2fP0j2d6ukPxSyIwE1i0SOokPgh2ho9az2i1JuEJnAJdC
+Jk7anTWqVIVDqY24BPdCAq3Tevvw6ZxYTX5syhQ06rP5fmA9qRvaJMj96bUk2I7U
+AwEJCYiHBBgTCgAPAhsMBQJWjWxuBQkDvpW5AAoJEE6N3A7eXSOmEE4Bfj2IlY+w
+66UuDaZubytEJ7RqZjxroSTygbFzmrrPcyeJo34spa97on2RJa3I1SUEqAF/ZXkY
+dwnMIGjsQFhmp0v0SeQqRKzQ0UsP4X/fCMhXcq1qvSZvp0xGd88OIawGL5YauDME
+VLBHDxYJKwYBBAHaRw8BAQdAN3zPDRahp3XZ2Q6SJcE4s36GziDIEOLq+jYZVTjG
+mQ6I5wQYEwoADwIbAgUCVo1sjAUJA75Y/QBqXyAEGRYKAAYFAlSwRw8ACgkQzehE
+GPNsf3t+2wEA4ZD2Fpw9oZuDVLKUfY2/7WvG9MFTJmhO7j6N2C8rdYgBAIm15ezk
+A2DUCxDKmufsYrkp73nyB/5jq0++qdFXI2oMCRBOjdwO3l0jpm9FAX49J1fL41ZU
+7QpJFEd8gJbYwjCu93YuIP6xpgSfNzE7y4Yl3fYEihmo21aEpeuxIY4BfiNY6r/z
+e5MEKgkw5EYZZK295zhyzZUh0k+0Aezlj1G9faAWgasI+Hp4ATIicNunng==
+=uwJN
+-----END PGP PUBLIC KEY BLOCK-----
diff --git a/public/assets/jonnybarnes-public-key.asc b/public/assets/jonnybarnes-public-key.asc
new file mode 100644
index 00000000..25e990db
--- /dev/null
+++ b/public/assets/jonnybarnes-public-key.asc
@@ -0,0 +1,52 @@
+-----BEGIN PGP PUBLIC KEY BLOCK-----
+Version: GnuPG v2
+
+mQINBFIpsg4BEAD/N8gCtJLVmLFNZeS9x7NcDJKAhYYLeFcNzucVKG0Q+XsLl9Ly
+u5YM4fNqKcHNSLz31lFFgM0kN6WLteCD2oRZJpKkgviitNXwX19om8rYMV7FqYBu
+GDVN6yfXLEUwa/NzsiHSr0YckFphpmdQPYkWJappcaGUzgl9sgy1uc0876NNllSU
+TA4XAgDNHydMTlXZT7b/+ttTUXkZBcl5LRBxeDx77sngkDUlZVIlD8b7fSvSNxec
+1U+aWAbnnzntDNXJ8Sve0NE3ti8GeOqeg0wWbzh9HMjadb5lX7nmATKGehefIaUv
+9D2GYk0LB/6wJVg0NK/SQmyBEB5jW3y0cI2il3NLC72qWU3J65Bl5AF0McKCBcDn
+kA5zISL8lSWXRcCnM5rf13Lz8pAOIEnbURSRILXWdXuzd2+55HaJaZFj9fQWuFZf
+3B/Rpr5rZskjdRgNToUEFYycEg70sCUnyl57oIWjQ5SWI0vqm8IaJO/wv0pSfdOC
+NpOl7ra3BZmULuXzcfR1C4akQAxkwRB2E1cG/eHoDdPSb6s/CBqM14EscHbUel5j
+xqRXc1uxRn7MOL6a8d/nE3iF8tUUPnXAwDgOZXKN2ZDfVjA9nKVuxOZka+lm2Phu
+2M3VGSzHWkJViDKZJ5XH3bYZv1ZhsjAHHM31+LwmTiy2NYxJVG+JOYsM6QARAQAB
+tDNKb25hdGhhbiBNYXggQmFybmVzIChKb25ueSkgPGpvbm55QGpvbm55YmFybmVz
+Lm5ldD6JAj0EEwEIACcFAlIpsg4CGwMFCQHhM4AFCwkIBwMFFQoJCAsFFgIDAQAC
+HgECF4AACgkQbBPmXsq8FiXn7hAAu1ssIlEtmaoVGMpmTMezqR36+/+2URfj9jah
+5TqTVD0GPnyLxD3dEQ24zmgAHHgWOYSlc/gO5AA9Ck2FNhtlqtgADBLr0dIYEVxy
+f0WckljxQ1UFGO+fDyJby2Q0lMQ+4qInY8X6G0UZ+/FhCO/7FtEdm4DKa0Kq5bof
+Iea1bL1oYb/59ZFc4mk7afQWBsXIh9HCqBik0qQ9j8LJDlEOchx8s2EaqRHO3i6P
+hRP9ceIiQrCmang7cmgfsXNe5KU9n2QFmLkiZxlH7YLSvEk2i6XerHrAduUyg+9j
+Yzkgl1oRju1UPglTN7KfCIiI/2wzu8LRZvZFoB8uXkTFtf0Dex5VIkdKyHBQ/09l
+o35+RTjviOybXv8jHYViXXpRrTx2rDLvilyueezHDDyrhEoU+19+JEzlc4bnSMes
+k2QIuXtqiKThhCxUS3tnkTRmVy9NMsWU3TcBC8sXwCJYqWFexOl04K/jfLNEBz7M
+Rj4fdo/GF8RmB/+9Kze5Rafha4pRQ5u/eevmGCxslaJe43JwV+ARNOzH7oDIPDiF
+0xvxvP+8/zMF2qsR66o12veOfIcqwtXebr/K4yngfexKzKY7LJ+i3Gz+q+KFInid
+HHwbX3rDamFdH4rSNavywg3AG6fTSDHgf8YLOPjkwHk8McWEXwcewSBE5tSNq+8O
+ZA2xoKO5Ag0EUimyDgEQAOu/anBLz4xaEw+T9jBMnqdDptJCRkT14LJFTudJK6rE
+D0qqnxNHWs+fDFs32RJSfpy8zMa+xCV0h9L2xbgI5PuWNFifQBcf+w9DWqor41Ta
+MyRuixSRWpM5UNDVjFoVa/hPiWCxZMC7K73pQ6lFyxzlAz9iJXOG+dT7GNeLjiUX
+AvBWRsfVIaV9bSNXK+o5Yb6UFwtCzeGcT0mCDs4Pc5cv3M+Kd5CDyfPPWJtGH9Fz
+2I1mAEemzP4Rgab7q9Ms1J1Tv/H5P1kaVrmdTFxbX6vi8hmnymDY5IPCWmRjBTaH
+xI9oby6Y+ExUwr1znD0pZBndJzq00hmxQIFFZDdlgqvTgJFtgvHEtjSMhMh5Cnkm
+JkSarxDpnTzKvBCUvPQeBnl8vq78jYIB9rLvmZPiwqoqnURBgKXJl+MGjhixHrmU
+hCrt0kOFX/w0APgueFXVcuBoxAQZx+uaFDrID9oJvXB7LGNnGhLd2aglz5a+kpB3
+pbmij0ercumIz3+NcGiili5zk2VHMfOI9rfqMqYmq9R9qoRancqd9CgMd5z9x999
+Ztj9GVs3rUhi4kVCzZbQLD/w7u22m+UsFEp7dbvHaaM2446k9pIcZZoUJLAmkgHQ
+dz9i9cOB3DEqo3zM2bZaHXaPIhunbzT80kiuggFxrDL00Hsm5WKZXnEX3wLoG+QD
+ABEBAAGJAiUEGAEIAA8FAlIpsg4CGwwFCQHhM4AACgkQbBPmXsq8FiX/KQ/+IzJB
+FJ15zw4gE4YOsNF7NQ5Bp8m45082ZhNVjexbPI1J6b+kFCW7lMqY2mBFlMidiheb
+RxMV5iNqrR1oXoSREo94z58X5+j1eIjjXk3TGUSuQ/4hG1yYuqrqsNpdLCmcnt5j
+7CbJ/KxnyPDltMwsMv5iS3ZDPqYHVWUieuiVss5HbFbEA9x3Hrzy7kxQNI33ZfwF
+dKNQv+Qz9dGLhygHuyPKc6e5jd6uw89pITkrnVdbc08miwGV/HlwQKNGyj2Ygwza
+a7QfhFV18qI+6ylZjdu/Kp6M8TxCaswhDhrwGtZh7v3o8HHJrKYD2Hb7IP9+K84t
+mkIII69Sbc9ZItIFzZMcyfk0RfeDy96oZUNvLc77OKbPCtD9/ZRObwxWc19s/LIJ
+sD76WbwlrR2yUnX2HYf6DxRNvxeSwjGzcqRJCZgg8LsylH2oDE+AdLKxbelDqqyT
+NtHhfF5W0URVnHxmjUBVjFxYtH8hSxSL+zOLUY6QXgtriRQa9PuQfbojlX9k3gtM
+9+53u5xtQoSwPeHczwxenYBddBGo9TsXWSvMVUQ8hlepTK3O57C1Sf+wAxW/EHeO
+30lNXDREr/J/IJ7AanL8fkUi7iaoBkx0Xdi3qe1YOu6MR1TuPT75cDoAjQEG2AUr
+igdpfiESmrELanSGdSsSBtieo+imJ3G3xmj2vrs=
+=dnNH
+-----END PGP PUBLIC KEY BLOCK-----
diff --git a/public/assets/js/Autolinker.min.js b/public/assets/js/Autolinker.min.js
new file mode 100644
index 00000000..e4f6366a
--- /dev/null
+++ b/public/assets/js/Autolinker.min.js
@@ -0,0 +1,10 @@
+/*!
+ * Autolinker.js
+ * 0.24.0
+ *
+ * Copyright(c) 2016 Gregory Jacobs 
+ * MIT
+ *
+ * https://github.com/gregjacobs/Autolinker.js
+ */
+!function(a,b){"function"==typeof define&&define.amd?define([],function(){return a.Autolinker=b()}):"object"==typeof exports?module.exports=b():a.Autolinker=b()}(this,function(){var a=function(a){a=a||{},this.urls=this.normalizeUrlsCfg(a.urls),this.email="boolean"==typeof a.email?a.email:!0,this.twitter="boolean"==typeof a.twitter?a.twitter:!0,this.phone="boolean"==typeof a.phone?a.phone:!0,this.hashtag=a.hashtag||!1,this.newWindow="boolean"==typeof a.newWindow?a.newWindow:!0,this.stripPrefix="boolean"==typeof a.stripPrefix?a.stripPrefix:!0;var b=this.hashtag;if(b!==!1&&"twitter"!==b&&"facebook"!==b&&"instagram"!==b)throw new Error("invalid `hashtag` cfg - see docs");this.truncate=this.normalizeTruncateCfg(a.truncate),this.className=a.className||"",this.replaceFn=a.replaceFn||null,this.htmlParser=null,this.matchers=null,this.tagBuilder=null};return a.prototype={constructor:a,normalizeUrlsCfg:function(a){return null==a&&(a=!0),"boolean"==typeof a?{schemeMatches:a,wwwMatches:a,tldMatches:a}:{schemeMatches:"boolean"==typeof a.schemeMatches?a.schemeMatches:!0,wwwMatches:"boolean"==typeof a.wwwMatches?a.wwwMatches:!0,tldMatches:"boolean"==typeof a.tldMatches?a.tldMatches:!0}},normalizeTruncateCfg:function(b){return"number"==typeof b?{length:b,location:"end"}:a.Util.defaults(b||{},{length:Number.POSITIVE_INFINITY,location:"end"})},parse:function(a){for(var b=this.getHtmlParser(),c=b.parse(a),d=0,e=[],f=0,g=c.length;g>f;f++){var h=c[f],i=h.getType();if("element"===i&&"a"===h.getTagName())h.isClosing()?d=Math.max(d-1,0):d++;else if("text"===i&&0===d){var j=this.parseText(h.getText(),h.getOffset());e.push.apply(e,j)}}return e=this.compactMatches(e),this.hashtag||(e=e.filter(function(a){return"hashtag"!==a.getType()})),this.email||(e=e.filter(function(a){return"email"!==a.getType()})),this.phone||(e=e.filter(function(a){return"phone"!==a.getType()})),this.twitter||(e=e.filter(function(a){return"twitter"!==a.getType()})),this.urls.schemeMatches||(e=e.filter(function(a){return"url"!==a.getType()||"scheme"!==a.getUrlMatchType()})),this.urls.wwwMatches||(e=e.filter(function(a){return"url"!==a.getType()||"www"!==a.getUrlMatchType()})),this.urls.tldMatches||(e=e.filter(function(a){return"url"!==a.getType()||"tld"!==a.getUrlMatchType()})),e},compactMatches:function(a){a.sort(function(a,b){return a.getOffset()-b.getOffset()});for(var b=0;be;e++){for(var g=c[e].parseMatches(a),h=0,i=g.length;i>h;h++)g[h].setOffset(b+g[h].getOffset());d.push.apply(d,g)}return d},link:function(a){if(!a)return"";for(var b=this.parse(a),c=[],d=0,e=0,f=b.length;f>e;e++){var g=b[e];c.push(a.substring(d,g.getOffset())),c.push(this.createMatchReturnVal(g)),d=g.getOffset()+g.getMatchedText().length}return c.push(a.substring(d)),c.join("")},createMatchReturnVal:function(b){var c;if(this.replaceFn&&(c=this.replaceFn.call(this,this,b)),"string"==typeof c)return c;if(c===!1)return b.getMatchedText();if(c instanceof a.HtmlTag)return c.toAnchorString();var d=this.getTagBuilder(),e=d.build(b);return e.toAnchorString()},getHtmlParser:function(){var b=this.htmlParser;return b||(b=this.htmlParser=new a.htmlParser.HtmlParser),b},getMatchers:function(){if(this.matchers)return this.matchers;var b=a.matcher,c=[new b.Hashtag({serviceName:this.hashtag}),new b.Email,new b.Phone,new b.Twitter,new b.Url({stripPrefix:this.stripPrefix})];return this.matchers=c},getTagBuilder:function(){var b=this.tagBuilder;return b||(b=this.tagBuilder=new a.AnchorTagBuilder({newWindow:this.newWindow,truncate:this.truncate,className:this.className})),b}},a.link=function(b,c){var d=new a(c);return d.link(b)},a.match={},a.matcher={},a.htmlParser={},a.truncate={},a.Util={abstractMethod:function(){throw"abstract"},trimRegex:/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,assign:function(a,b){for(var c in b)b.hasOwnProperty(c)&&(a[c]=b[c]);return a},defaults:function(a,b){for(var c in b)b.hasOwnProperty(c)&&void 0===a[c]&&(a[c]=b[c]);return a},extend:function(b,c){var d=b.prototype,e=function(){};e.prototype=d;var f;f=c.hasOwnProperty("constructor")?c.constructor:function(){d.constructor.apply(this,arguments)};var g=f.prototype=new e;return g.constructor=f,g.superclass=d,delete c.constructor,a.Util.assign(g,c),f},ellipsis:function(a,b,c){return a.length>b&&(c=null==c?"..":c,a=a.substring(0,b-c.length)+c),a},indexOf:function(a,b){if(Array.prototype.indexOf)return a.indexOf(b);for(var c=0,d=a.length;d>c;c++)if(a[c]===b)return c;return-1},splitAndCapture:function(a,b){if(!b.global)throw new Error("`splitRegex` must have the 'g' flag set");for(var c,d=[],e=0;c=b.exec(a);)d.push(a.substring(e,c.index)),d.push(c[0]),e=c.index+c[0].length;return d.push(a.substring(e)),d},trim:function(a){return a.replace(this.trimRegex,"")}},a.HtmlTag=a.Util.extend(Object,{whitespaceRegex:/\s+/,constructor:function(b){a.Util.assign(this,b),this.innerHtml=this.innerHtml||this.innerHTML},setTagName:function(a){return this.tagName=a,this},getTagName:function(){return this.tagName||""},setAttr:function(a,b){var c=this.getAttrs();return c[a]=b,this},getAttr:function(a){return this.getAttrs()[a]},setAttrs:function(b){var c=this.getAttrs();return a.Util.assign(c,b),this},getAttrs:function(){return this.attrs||(this.attrs={})},setClass:function(a){return this.setAttr("class",a)},addClass:function(b){for(var c,d=this.getClass(),e=this.whitespaceRegex,f=a.Util.indexOf,g=d?d.split(e):[],h=b.split(e);c=h.shift();)-1===f(g,c)&&g.push(c);return this.getAttrs()["class"]=g.join(" "),this},removeClass:function(b){for(var c,d=this.getClass(),e=this.whitespaceRegex,f=a.Util.indexOf,g=d?d.split(e):[],h=b.split(e);g.length&&(c=h.shift());){var i=f(g,c);-1!==i&&g.splice(i,1)}return this.getAttrs()["class"]=g.join(" "),this},getClass:function(){return this.getAttrs()["class"]||""},hasClass:function(a){return-1!==(" "+this.getClass()+" ").indexOf(" "+a+" ")},setInnerHtml:function(a){return this.innerHtml=a,this},getInnerHtml:function(){return this.innerHtml||""},toAnchorString:function(){var a=this.getTagName(),b=this.buildAttrsStr();return b=b?" "+b:"",["<",a,b,">",this.getInnerHtml(),""].join("")},buildAttrsStr:function(){if(!this.attrs)return"";var a=this.getAttrs(),b=[];for(var c in a)a.hasOwnProperty(c)&&b.push(c+'="'+a[c]+'"');return b.join(" ")}}),a.RegexLib=function(){var a="A-Za-zªµºÀ-ÖØ-öø-ˁˆ-ˑˠ-ˤˬˮͰ-ʹͶͷͺ-ͽͿΆΈ-ΊΌΎ-ΡΣ-ϵϷ-ҁҊ-ԯԱ-Ֆՙա-ևא-תװ-ײؠ-يٮٯٱ-ۓەۥۦۮۯۺ-ۼۿܐܒ-ܯݍ-ޥޱߊ-ߪߴߵߺࠀ-ࠕࠚࠤࠨࡀ-ࡘࢠ-ࢴऄ-हऽॐक़-ॡॱ-ঀঅ-ঌএঐও-নপ-রলশ-হঽৎড়ঢ়য়-ৡৰৱਅ-ਊਏਐਓ-ਨਪ-ਰਲਲ਼ਵਸ਼ਸਹਖ਼-ੜਫ਼ੲ-ੴઅ-ઍએ-ઑઓ-નપ-રલળવ-હઽૐૠૡૹଅ-ଌଏଐଓ-ନପ-ରଲଳଵ-ହଽଡ଼ଢ଼ୟ-ୡୱஃஅ-ஊஎ-ஐஒ-கஙசஜஞடணதந-பம-ஹௐఅ-ఌఎ-ఐఒ-నప-హఽౘ-ౚౠౡಅ-ಌಎ-ಐಒ-ನಪ-ಳವ-ಹಽೞೠೡೱೲഅ-ഌഎ-ഐഒ-ഺഽൎൟ-ൡൺ-ൿඅ-ඖක-නඳ-රලව-ෆก-ะาำเ-ๆກຂຄງຈຊຍດ-ທນ-ຟມ-ຣລວສຫອ-ະາຳຽເ-ໄໆໜ-ໟༀཀ-ཇཉ-ཬྈ-ྌက-ဪဿၐ-ၕၚ-ၝၡၥၦၮ-ၰၵ-ႁႎႠ-ჅჇჍა-ჺჼ-ቈቊ-ቍቐ-ቖቘቚ-ቝበ-ኈኊ-ኍነ-ኰኲ-ኵኸ-ኾዀዂ-ዅወ-ዖዘ-ጐጒ-ጕጘ-ፚᎀ-ᎏᎠ-Ᏽᏸ-ᏽᐁ-ᙬᙯ-ᙿᚁ-ᚚᚠ-ᛪᛱ-ᛸᜀ-ᜌᜎ-ᜑᜠ-ᜱᝀ-ᝑᝠ-ᝬᝮ-ᝰក-ឳៗៜᠠ-ᡷᢀ-ᢨᢪᢰ-ᣵᤀ-ᤞᥐ-ᥭᥰ-ᥴᦀ-ᦫᦰ-ᧉᨀ-ᨖᨠ-ᩔᪧᬅ-ᬳᭅ-ᭋᮃ-ᮠᮮᮯᮺ-ᯥᰀ-ᰣᱍ-ᱏᱚ-ᱽᳩ-ᳬᳮ-ᳱᳵᳶᴀ-ᶿḀ-ἕἘ-Ἕἠ-ὅὈ-Ὅὐ-ὗὙὛὝὟ-ώᾀ-ᾴᾶ-ᾼιῂ-ῄῆ-ῌῐ-ΐῖ-Ίῠ-Ῥῲ-ῴῶ-ῼⁱⁿₐ-ₜℂℇℊ-ℓℕℙ-ℝℤΩℨK-ℭℯ-ℹℼ-ℿⅅ-ⅉⅎↃↄⰀ-Ⱞⰰ-ⱞⱠ-ⳤⳫ-ⳮⳲⳳⴀ-ⴥⴧⴭⴰ-ⵧⵯⶀ-ⶖⶠ-ⶦⶨ-ⶮⶰ-ⶶⶸ-ⶾⷀ-ⷆⷈ-ⷎⷐ-ⷖⷘ-ⷞⸯ々〆〱-〵〻〼ぁ-ゖゝ-ゟァ-ヺー-ヿㄅ-ㄭㄱ-ㆎㆠ-ㆺㇰ-ㇿ㐀-䶵一-鿕ꀀ-ꒌꓐ-ꓽꔀ-ꘌꘐ-ꘟꘪꘫꙀ-ꙮꙿ-ꚝꚠ-ꛥꜗ-ꜟꜢ-ꞈꞋ-ꞭꞰ-ꞷꟷ-ꠁꠃ-ꠅꠇ-ꠊꠌ-ꠢꡀ-ꡳꢂ-ꢳꣲ-ꣷꣻꣽꤊ-ꤥꤰ-ꥆꥠ-ꥼꦄ-ꦲꧏꧠ-ꧤꧦ-ꧯꧺ-ꧾꨀ-ꨨꩀ-ꩂꩄ-ꩋꩠ-ꩶꩺꩾ-ꪯꪱꪵꪶꪹ-ꪽꫀꫂꫛ-ꫝꫠ-ꫪꫲ-ꫴꬁ-ꬆꬉ-ꬎꬑ-ꬖꬠ-ꬦꬨ-ꬮꬰ-ꭚꭜ-ꭥꭰ-ꯢ가-힣ힰ-ퟆퟋ-ퟻ豈-舘並-龎ff-stﬓ-ﬗיִײַ-ﬨשׁ-זּטּ-לּמּנּסּףּפּצּ-ﮱﯓ-ﴽﵐ-ﶏﶒ-ﷇﷰ-ﷻﹰ-ﹴﹶ-ﻼA-Za-zヲ-하-ᅦᅧ-ᅬᅭ-ᅲᅳ-ᅵ",b="0-9٠-٩۰-۹߀-߉०-९০-৯੦-੯૦-૯୦-୯௦-௯౦-౯೦-೯൦-൯෦-෯๐-๙໐-໙༠-༩၀-၉႐-႙០-៩᠐-᠙᥆-᥏᧐-᧙᪀-᪉᪐-᪙᭐-᭙᮰-᮹᱀-᱉᱐-᱙꘠-꘩꣐-꣙꤀-꤉꧐-꧙꧰-꧹꩐-꩙꯰-꯹0-9",c=a+b,d=new RegExp("["+c+".\\-]*["+c+"\\-]"),e=/(?:international|construction|contractors|enterprises|photography|productions|foundation|immobilien|industries|management|properties|technology|christmas|community|directory|education|equipment|institute|marketing|solutions|vacations|bargains|boutique|builders|catering|cleaning|clothing|computer|democrat|diamonds|graphics|holdings|lighting|partners|plumbing|supplies|training|ventures|academy|careers|company|cruises|domains|exposed|flights|florist|gallery|guitars|holiday|kitchen|neustar|okinawa|recipes|rentals|reviews|shiksha|singles|support|systems|agency|berlin|camera|center|coffee|condos|dating|estate|events|expert|futbol|kaufen|luxury|maison|monash|museum|nagoya|photos|repair|report|social|supply|tattoo|tienda|travel|viajes|villas|vision|voting|voyage|actor|build|cards|cheap|codes|dance|email|glass|house|mango|ninja|parts|photo|press|shoes|solar|today|tokyo|tools|watch|works|aero|arpa|asia|best|bike|blue|buzz|camp|club|cool|coop|farm|fish|gift|guru|info|jobs|kiwi|kred|land|limo|link|menu|mobi|moda|name|pics|pink|post|qpon|rich|ruhr|sexy|tips|vote|voto|wang|wien|wiki|zone|bar|bid|biz|cab|cat|ceo|com|edu|gov|int|kim|mil|net|onl|org|pro|pub|red|tel|uno|wed|xxx|xyz|ac|ad|ae|af|ag|ai|al|am|an|ao|aq|ar|as|at|au|aw|ax|az|ba|bb|bd|be|bf|bg|bh|bi|bj|bm|bn|bo|br|bs|bt|bv|bw|by|bz|ca|cc|cd|cf|cg|ch|ci|ck|cl|cm|cn|co|cr|cu|cv|cw|cx|cy|cz|de|dj|dk|dm|do|dz|ec|ee|eg|er|es|et|eu|fi|fj|fk|fm|fo|fr|ga|gb|gd|ge|gf|gg|gh|gi|gl|gm|gn|gp|gq|gr|gs|gt|gu|gw|gy|hk|hm|hn|hr|ht|hu|id|ie|il|im|in|io|iq|ir|is|it|je|jm|jo|jp|ke|kg|kh|ki|km|kn|kp|kr|kw|ky|kz|la|lb|lc|li|lk|lr|ls|lt|lu|lv|ly|ma|mc|md|me|mg|mh|mk|ml|mm|mn|mo|mp|mq|mr|ms|mt|mu|mv|mw|mx|my|mz|na|nc|ne|nf|ng|ni|nl|no|np|nr|nu|nz|om|pa|pe|pf|pg|ph|pk|pl|pm|pn|pr|ps|pt|pw|py|qa|re|ro|rs|ru|rw|sa|sb|sc|sd|se|sg|sh|si|sj|sk|sl|sm|sn|so|sr|st|su|sv|sx|sy|sz|tc|td|tf|tg|th|tj|tk|tl|tm|tn|to|tp|tr|tt|tv|tw|tz|ua|ug|uk|us|uy|uz|va|vc|ve|vg|vi|vn|vu|wf|ws|ye|yt|za|zm|zw)\b/;return{alphaNumericCharsStr:c,domainNameRegex:d,tldRegex:e}}(),a.AnchorTagBuilder=a.Util.extend(Object,{constructor:function(b){a.Util.assign(this,b)},build:function(b){return new a.HtmlTag({tagName:"a",attrs:this.createAttrs(b.getType(),b.getAnchorHref()),innerHtml:this.processAnchorText(b.getAnchorText())})},createAttrs:function(a,b){var c={href:b},d=this.createCssClass(a);return d&&(c["class"]=d),this.newWindow&&(c.target="_blank"),c},createCssClass:function(a){var b=this.className;return b?b+" "+b+"-"+a:""},processAnchorText:function(a){return a=this.doTruncate(a)},doTruncate:function(b){var c=this.truncate;if(!c)return b;var d=c.length,e=c.location;return"smart"===e?a.truncate.TruncateSmart(b,d,".."):"middle"===e?a.truncate.TruncateMiddle(b,d,".."):a.truncate.TruncateEnd(b,d,"..")}}),a.htmlParser.HtmlParser=a.Util.extend(Object,{htmlRegex:function(){var a=/!--([\s\S]+?)--/,b=/[0-9a-zA-Z][0-9a-zA-Z:]*/,c=/[^\s\0"'>\/=\x01-\x1F\x7F]+/,d=/(?:"[^"]*?"|'[^']*?'|[^'"=<>`\s]+)/,e=c.source+"(?:\\s*=\\s*"+d.source+")?";return new RegExp(["(?:","<(!DOCTYPE)","(?:","\\s+","(?:",e,"|",d.source+")",")*",">",")","|","(?:","<(/)?","(?:",a.source,"|","(?:","("+b.source+")","(?:","\\s+",e,")*","\\s*/?",")",")",">",")"].join(""),"gi")}(),htmlCharacterEntitiesRegex:/( | |<|<|>|>|"|"|')/gi,parse:function(a){for(var b,c,d=this.htmlRegex,e=0,f=[];null!==(b=d.exec(a));){var g=b[0],h=b[3],i=b[1]||b[4],j=!!b[2],k=b.index,l=a.substring(e,k);l&&(c=this.parseTextAndEntityNodes(e,l),f.push.apply(f,c)),f.push(h?this.createCommentNode(k,g,h):this.createElementNode(k,g,i,j)),e=k+g.length}if(ef;f+=2){var h=e[f],i=e[f+1];h&&(d.push(this.createTextNode(b,h)),b+=h.length),i&&(d.push(this.createEntityNode(b,i)),b+=i.length)}return d},createCommentNode:function(b,c,d){return new a.htmlParser.CommentNode({offset:b,text:c,comment:a.Util.trim(d)})},createElementNode:function(b,c,d,e){return new a.htmlParser.ElementNode({offset:b,text:c,tagName:d.toLowerCase(),closing:e})},createEntityNode:function(b,c){return new a.htmlParser.EntityNode({offset:b,text:c})},createTextNode:function(b,c){return new a.htmlParser.TextNode({offset:b,text:c})}}),a.htmlParser.HtmlNode=a.Util.extend(Object,{offset:void 0,text:void 0,constructor:function(b){if(a.Util.assign(this,b),null==this.offset)throw new Error("`offset` cfg required");if(null==this.text)throw new Error("`text` cfg required")},getType:a.Util.abstractMethod,getOffset:function(){return this.offset},getText:function(){return this.text}}),a.htmlParser.CommentNode=a.Util.extend(a.htmlParser.HtmlNode,{comment:"",getType:function(){return"comment"},getComment:function(){return this.comment}}),a.htmlParser.ElementNode=a.Util.extend(a.htmlParser.HtmlNode,{tagName:"",closing:!1,getType:function(){return"element"},getTagName:function(){return this.tagName},isClosing:function(){return this.closing}}),a.htmlParser.EntityNode=a.Util.extend(a.htmlParser.HtmlNode,{getType:function(){return"entity"}}),a.htmlParser.TextNode=a.Util.extend(a.htmlParser.HtmlNode,{getType:function(){return"text"}}),a.match.Match=a.Util.extend(Object,{constructor:function(a,b){if(null==a)throw new Error("`matchedText` arg required");if(null==b)throw new Error("`offset` arg required");this.matchedText=a,this.offset=b},getType:a.Util.abstractMethod,getMatchedText:function(){return this.matchedText},setOffset:function(a){this.offset=a},getOffset:function(){return this.offset},getAnchorHref:a.Util.abstractMethod,getAnchorText:a.Util.abstractMethod}),a.match.Email=a.Util.extend(a.match.Match,{constructor:function(b,c,d){if(a.match.Match.prototype.constructor.call(this,b,c),!d)throw new Error("`email` arg required");this.email=d},getType:function(){return"email"},getEmail:function(){return this.email},getAnchorHref:function(){return"mailto:"+this.email},getAnchorText:function(){return this.email}}),a.match.Hashtag=a.Util.extend(a.match.Match,{constructor:function(b,c,d,e){if(a.match.Match.prototype.constructor.call(this,b,c),!e)throw new Error("`hashtag` arg required");this.serviceName=d,this.hashtag=e},getType:function(){return"hashtag"},getServiceName:function(){return this.serviceName},getHashtag:function(){return this.hashtag},getAnchorHref:function(){var a=this.serviceName,b=this.hashtag;switch(a){case"twitter":return"https://twitter.com/hashtag/"+b;case"facebook":return"https://www.facebook.com/hashtag/"+b;case"instagram":return"https://instagram.com/explore/tags/"+b;default:throw new Error("Unknown service name to point hashtag to: ",a)}},getAnchorText:function(){return"#"+this.hashtag}}),a.match.Phone=a.Util.extend(a.match.Match,{constructor:function(b,c,d,e){if(a.match.Match.prototype.constructor.call(this,b,c),!d)throw new Error("`number` arg required");if(null==e)throw new Error("`plusSign` arg required");this.number=d,this.plusSign=e},getType:function(){return"phone"},getNumber:function(){return this.number},getAnchorHref:function(){return"tel:"+(this.plusSign?"+":"")+this.number},getAnchorText:function(){return this.matchedText}}),a.match.Twitter=a.Util.extend(a.match.Match,{constructor:function(b,c,d){if(a.match.Match.prototype.constructor.call(this,b,c),!d)throw new Error("`twitterHandle` arg required");this.twitterHandle=d},getType:function(){return"twitter"},getTwitterHandle:function(){return this.twitterHandle},getAnchorHref:function(){return"https://twitter.com/"+this.twitterHandle},getAnchorText:function(){return"@"+this.twitterHandle}}),a.match.Url=a.Util.extend(a.match.Match,{constructor:function(b,c,d,e,f,g,h){if(a.match.Match.prototype.constructor.call(this,b,c),"scheme"!==e&&"www"!==e&&"tld"!==e)throw new Error('`urlMatchType` must be one of: "scheme", "www", or "tld"');if(!d)throw new Error("`url` arg required");if(null==f)throw new Error("`protocolUrlMatch` arg required");if(null==g)throw new Error("`protocolRelativeMatch` arg required");if(null==h)throw new Error("`stripPrefix` arg required");this.urlMatchType=e,this.url=d,this.protocolUrlMatch=f,this.protocolRelativeMatch=g,this.stripPrefix=h},urlPrefixRegex:/^(https?:\/\/)?(www\.)?/i,protocolRelativeRegex:/^\/\//,protocolPrepended:!1,getType:function(){return"url"},getUrlMatchType:function(){return this.urlMatchType},getUrl:function(){var a=this.url;return this.protocolRelativeMatch||this.protocolUrlMatch||this.protocolPrepended||(a=this.url="http://"+a,this.protocolPrepended=!0),a},getAnchorHref:function(){var a=this.getUrl();return a.replace(/&/g,"&")},getAnchorText:function(){var a=this.getMatchedText();return this.protocolRelativeMatch&&(a=this.stripProtocolRelativePrefix(a)),this.stripPrefix&&(a=this.stripUrlPrefix(a)),a=this.removeTrailingSlash(a)},stripUrlPrefix:function(a){return a.replace(this.urlPrefixRegex,"")},stripProtocolRelativePrefix:function(a){return a.replace(this.protocolRelativeRegex,"")},removeTrailingSlash:function(a){return"/"===a.charAt(a.length-1)&&(a=a.slice(0,-1)),a}}),a.matcher.Matcher=a.Util.extend(Object,{constructor:function(b){a.Util.assign(this,b)},parseMatches:a.Util.abstractMethod}),a.matcher.Email=a.Util.extend(a.matcher.Matcher,{matcherRegex:function(){var b=a.RegexLib.alphaNumericCharsStr,c=new RegExp("["+b+"\\-;:&=+$.,]+@"),d=a.RegexLib.domainNameRegex,e=a.RegexLib.tldRegex;return new RegExp([c.source,d.source,"\\.",e.source].join(""),"gi")}(),parseMatches:function(b){for(var c,d=this.matcherRegex,e=[];null!==(c=d.exec(b));){var f=c[0];e.push(new a.match.Email(f,c.index,f))}return e}}),a.matcher.Hashtag=a.Util.extend(a.matcher.Matcher,{matcherRegex:new RegExp("#[_"+a.RegexLib.alphaNumericCharsStr+"]{1,139}","g"),nonWordCharRegex:new RegExp("[^"+a.RegexLib.alphaNumericCharsStr+"]"),constructor:function(){a.matcher.Matcher.prototype.constructor.apply(this,arguments)},parseMatches:function(b){for(var c,d=this.matcherRegex,e=this.nonWordCharRegex,f=this.serviceName,g=[];null!==(c=d.exec(b));){var h=c.index,i=b.charAt(h-1);if(0===h||e.test(i)){var j=c[0],k=c[0].slice(1);g.push(new a.match.Hashtag(j,h,f,k))}}return g}}),a.matcher.Phone=a.Util.extend(a.matcher.Matcher,{matcherRegex:/(?:(\+)?\d{1,3}[-\040.])?\(?\d{3}\)?[-\040.]?\d{3}[-\040.]\d{4}/g,parseMatches:function(b){for(var c,d=this.matcherRegex,e=[];null!==(c=d.exec(b));){var f=c[0],g=f.replace(/\D/g,""),h=!!c[1];e.push(new a.match.Phone(f,c.index,g,h))}return e}}),a.matcher.Twitter=a.Util.extend(a.matcher.Matcher,{matcherRegex:new RegExp("@[_"+a.RegexLib.alphaNumericCharsStr+"]{1,20}","g"),nonWordCharRegex:new RegExp("[^"+a.RegexLib.alphaNumericCharsStr+"]"),parseMatches:function(b){for(var c,d=this.matcherRegex,e=this.nonWordCharRegex,f=[];null!==(c=d.exec(b));){var g=c.index,h=b.charAt(g-1);if(0===g||e.test(h)){var i=c[0],j=c[0].slice(1);f.push(new a.match.Twitter(i,g,j))}}return f}}),a.matcher.Url=a.Util.extend(a.matcher.Matcher,{matcherRegex:function(){var b=/(?:[A-Za-z][-.+A-Za-z0-9]*:(?![A-Za-z][-.+A-Za-z0-9]*:\/\/)(?!\d+\/?)(?:\/\/)?)/,c=/(?:www\.)/,d=a.RegexLib.domainNameRegex,e=a.RegexLib.tldRegex,f=a.RegexLib.alphaNumericCharsStr,g=new RegExp("["+f+"\\-+&@#/%=~_()|'$*\\[\\]?!:,.;]*["+f+"\\-+&@#/%=~_()|'$*\\[\\]]");return new RegExp(["(?:","(",b.source,d.source,")","|","(","(//)?",c.source,d.source,")","|","(","(//)?",d.source+"\\.",e.source,")",")","(?:"+g.source+")?"].join(""),"gi")}(),wordCharRegExp:/\w/,openParensRe:/\(/g,closeParensRe:/\)/g,constructor:function(){if(a.matcher.Matcher.prototype.constructor.apply(this,arguments),null==this.stripPrefix)throw new Error("`stripPrefix` cfg required")},parseMatches:function(b){for(var c,d=this.matcherRegex,e=this.stripPrefix,f=[];null!==(c=d.exec(b));){var g=c[0],h=c[1],i=c[2],j=c[3],k=c[5],l=c.index,m=j||k,n=b.charAt(l-1);if(a.matcher.UrlMatchValidator.isValid(g,h)&&!(l>0&&"@"===n||l>0&&m&&this.wordCharRegExp.test(n))){if(this.matchHasUnbalancedClosingParen(g))g=g.substr(0,g.length-1);else{var o=this.matchHasInvalidCharAfterTld(g,h);o>-1&&(g=g.substr(0,o))}var p=h?"scheme":i?"www":"tld",q=!!h;f.push(new a.match.Url(g,l,g,p,q,!!m,e))}}return f},matchHasUnbalancedClosingParen:function(a){var b=a.charAt(a.length-1);if(")"===b){var c=a.match(this.openParensRe),d=a.match(this.closeParensRe),e=c&&c.length||0,f=d&&d.length||0;if(f>e)return!0}return!1},matchHasInvalidCharAfterTld:function(a,b){if(!a)return-1;var c=0;b&&(c=a.indexOf(":"),a=a.slice(c));var d=/^((.?\/\/)?[A-Za-z0-9\u00C0-\u017F\.\-]*[A-Za-z0-9\u00C0-\u017F\-]\.[A-Za-z]+)/,e=d.exec(a);return null===e?-1:(c+=e[1].length,a=a.slice(e[1].length),/^[^.A-Za-z:\/?#]/.test(a)?c:-1)}}),a.matcher.UrlMatchValidator={hasFullProtocolRegex:/^[A-Za-z][-.+A-Za-z0-9]*:\/\//,uriSchemeRegex:/^[A-Za-z][-.+A-Za-z0-9]*:/,hasWordCharAfterProtocolRegex:/:[^\s]*?[A-Za-z\u00C0-\u017F]/,isValid:function(a,b){return b&&!this.isValidUriScheme(b)||this.urlMatchDoesNotHaveProtocolOrDot(a,b)||this.urlMatchDoesNotHaveAtLeastOneWordChar(a,b)?!1:!0},isValidUriScheme:function(a){var b=a.match(this.uriSchemeRegex)[0].toLowerCase();return"javascript:"!==b&&"vbscript:"!==b},urlMatchDoesNotHaveProtocolOrDot:function(a,b){return!(!a||b&&this.hasFullProtocolRegex.test(b)||-1!==a.indexOf("."))},urlMatchDoesNotHaveAtLeastOneWordChar:function(a,b){return a&&b?!this.hasWordCharAfterProtocolRegex.test(a):!1}},a.truncate.TruncateEnd=function(b,c,d){return a.Util.ellipsis(b,c,d)},a.truncate.TruncateMiddle=function(a,b,c){if(a.length<=b)return a;var d=b-c.length,e="";return d>0&&(e=a.substr(-1*Math.floor(d/2))),(a.substr(0,Math.ceil(d/2))+c+e).substr(0,b)},a.truncate.TruncateSmart=function(a,b,c){var d=function(a){var b={},c=a,d=c.match(/^([a-z]+):\/\//i);return d&&(b.scheme=d[1],c=c.substr(d[0].length)),d=c.match(/^(.*?)(?=(\?|#|\/|$))/i),d&&(b.host=d[1],c=c.substr(d[0].length)),d=c.match(/^\/(.*?)(?=(\?|#|$))/i),d&&(b.path=d[1],c=c.substr(d[0].length)),d=c.match(/^\?(.*?)(?=(#|$))/i),d&&(b.query=d[1],c=c.substr(d[0].length)),d=c.match(/^#(.*?)$/i),d&&(b.fragment=d[1]),b},e=function(a){var b="";return a.scheme&&a.host&&(b+=a.scheme+"://"),a.host&&(b+=a.host),a.path&&(b+="/"+a.path),a.query&&(b+="?"+a.query),a.fragment&&(b+="#"+a.fragment),b},f=function(a,b){var d=b/2,e=Math.ceil(d),f=-1*Math.floor(d),g="";return 0>f&&(g=a.substr(f)),a.substr(0,e)+c+g};if(a.length<=b)return a;var g=b-c.length,h=d(a);if(h.query){var i=h.query.match(/^(.*?)(?=(\?|\#))(.*?)$/i);i&&(h.query=h.query.substr(0,i[1].length),a=e(h))}if(a.length<=b)return a;if(h.host&&(h.host=h.host.replace(/^www\./,""),a=e(h)),a.length<=b)return a;var j="";if(h.host&&(j+=h.host),j.length>=g)return h.host.length==b?(h.host.substr(0,b-c.length)+c).substr(0,b):f(j,g).substr(0,b);var k="";if(h.path&&(k+="/"+h.path),h.query&&(k+="?"+h.query),k){if((j+k).length>=g){if((j+k).length==b)return(j+k).substr(0,b);var l=g-j.length;return(j+f(k,l)).substr(0,b)}j+=k}if(h.fragment){var m="#"+h.fragment;if((j+m).length>=g){if((j+m).length==b)return(j+m).substr(0,b);var n=g-j.length;return(j+f(m,n)).substr(0,b)}j+=m}if(h.scheme&&h.host){var o=h.scheme+"://";if((j+o).length0&&(p=j.substr(-1*Math.floor(g/2))),(j.substr(0,Math.ceil(g/2))+c+p).substr(0,b)},a});
\ No newline at end of file
diff --git a/public/assets/js/alertify.js b/public/assets/js/alertify.js
new file mode 100644
index 00000000..d9d6a2b2
--- /dev/null
+++ b/public/assets/js/alertify.js
@@ -0,0 +1 @@
+!function(){"use strict";function t(){var t={version:"1.0.8",defaultOkLabel:"Ok",okLabel:"Ok",defaultCancelLabel:"Cancel",cancelLabel:"Cancel",defaultMaxLogItems:2,maxLogItems:2,promptValue:"",promptPlaceholder:"",closeLogOnClick:!1,closeLogOnClickDefault:!1,delay:5e3,defaultDelay:5e3,logContainerClass:"alertify-logs",logContainerDefaultClass:"alertify-logs",dialogs:{buttons:{holder:"",ok:"",cancel:""},input:"",message:"

{{message}}

",log:"
{{message}}
"},defaultDialogs:{buttons:{holder:"",ok:"",cancel:""},input:"",message:"

{{message}}

",log:"
{{message}}
"},build:function(t){var e=this.dialogs.buttons.ok,o="
"+this.dialogs.message.replace("{{message}}",t.message);return"confirm"!==t.type&&"prompt"!==t.type||(e=this.dialogs.buttons.cancel+this.dialogs.buttons.ok),"prompt"===t.type&&(o+=this.dialogs.input),o=(o+this.dialogs.buttons.holder+"
").replace("{{buttons}}",e).replace("{{ok}}",this.okLabel).replace("{{cancel}}",this.cancelLabel)},setCloseLogOnClick:function(t){this.closeLogOnClick=!!t},close:function(t,e){this.closeLogOnClick&&t.addEventListener("click",function(t){o(t.srcElement)}),e=e&&!isNaN(+e)?+e:this.delay,0>e?o(t):e>0&&setTimeout(function(){o(t)},e)},dialog:function(t,e,o,n){return this.setup({type:e,message:t,onOkay:o,onCancel:n})},log:function(t,e,o){var n=document.querySelectorAll(".alertify-logs > div");if(n){var i=n.length-this.maxLogItems;if(i>=0)for(var a=0,l=i+1;l>a;a++)this.close(n[a],-1)}this.notify(t,e,o)},setLogPosition:function(t){this.logContainerClass="alertify-logs "+t},setupLogContainer:function(){var t=document.querySelector(".alertify-logs"),e=this.logContainerClass;return t||(t=document.createElement("div"),t.className=e,document.body.appendChild(t)),t.className!==e&&(t.className=e),t},notify:function(e,o,n){var i=this.setupLogContainer(),a=document.createElement("div");a.className=o||"default",t.logTemplateMethod?a.innerHTML=t.logTemplateMethod(e):a.innerHTML=e,"function"==typeof n&&a.addEventListener("click",n),i.appendChild(a),setTimeout(function(){a.className+=" show"},10),this.close(a,this.delay)},setup:function(t){function e(e){"function"!=typeof e&&(e=function(){}),i&&i.addEventListener("click",function(i){t.onOkay&&"function"==typeof t.onOkay&&(l?t.onOkay(l.value,i):t.onOkay(i)),e(l?{buttonClicked:"ok",inputValue:l.value,event:i}:{buttonClicked:"ok",event:i}),o(n)}),a&&a.addEventListener("click",function(i){t.onCancel&&"function"==typeof t.onCancel&&t.onCancel(i),e({buttonClicked:"cancel",event:i}),o(n)})}var n=document.createElement("div");n.className="alertify hide",n.innerHTML=this.build(t);var i=n.querySelector(".ok"),a=n.querySelector(".cancel"),l=n.querySelector("input"),s=n.querySelector("label");l&&("string"==typeof this.promptPlaceholder&&(s?s.textContent=this.promptPlaceholder:l.placeholder=this.promptPlaceholder),"string"==typeof this.promptValue&&(l.value=this.promptValue));var r;return"function"==typeof Promise?r=new Promise(e):e(),document.body.appendChild(n),setTimeout(function(){n.classList.remove("hide"),l&&t.type&&"prompt"===t.type?(l.select(),l.focus()):i&&i.focus()},100),r},okBtn:function(t){return this.okLabel=t,this},setDelay:function(t){var e=parseInt(t||0,10);return this.delay=isNaN(e)?this.defultDelay:t,this},cancelBtn:function(t){return this.cancelLabel=t,this},setMaxLogItems:function(t){this.maxLogItems=parseInt(t||this.defaultMaxLogItems)},theme:function(t){switch(t.toLowerCase()){case"bootstrap":this.dialogs.buttons.ok="",this.dialogs.buttons.cancel="",this.dialogs.input="";break;case"purecss":this.dialogs.buttons.ok="",this.dialogs.buttons.cancel="";break;case"mdl":case"material-design-light":this.dialogs.buttons.ok="",this.dialogs.buttons.cancel="",this.dialogs.input="
";break;case"angular-material":this.dialogs.buttons.ok="",this.dialogs.buttons.cancel="",this.dialogs.input="
";break;case"default":default:this.dialogs.buttons.ok=this.defaultDialogs.buttons.ok,this.dialogs.buttons.cancel=this.defaultDialogs.buttons.cancel,this.dialogs.input=this.defaultDialogs.input}},reset:function(){this.theme("default"),this.okBtn(this.defaultOkLabel),this.cancelBtn(this.defaultCancelLabel),this.setMaxLogItems(),this.promptValue="",this.promptPlaceholder="",this.delay=this.defaultDelay,this.setCloseLogOnClick(this.closeLogOnClickDefault),this.setLogPosition("bottom left"),this.logTemplateMethod=null},injectCSS:function(){if(!document.querySelector("#alertifyCSS")){var t=document.getElementsByTagName("head")[0],e=document.createElement("style");e.type="text/css",e.id="alertifyCSS",e.innerHTML=".alertify-logs>*{padding:12px 24px;color:#fff;box-shadow:0 2px 5px 0 rgba(0,0,0,.2);border-radius:1px}.alertify-logs>*,.alertify-logs>.default{background:rgba(0,0,0,.8)}.alertify-logs>.error{background:rgba(244,67,54,.8)}.alertify-logs>.success{background:rgba(76,175,80,.9)}.alertify{position:fixed;background-color:rgba(0,0,0,.3);left:0;right:0;top:0;bottom:0;width:100%;height:100%;z-index:2}.alertify.hide{opacity:0;pointer-events:none}.alertify,.alertify.show{box-sizing:border-box;transition:all .33s cubic-bezier(.25,.8,.25,1)}.alertify,.alertify *{box-sizing:border-box}.alertify .dialog{padding:12px}.alertify .alert,.alertify .dialog{width:100%;margin:0 auto;position:relative;top:50%;-webkit-transform:translateY(-50%);transform:translateY(-50%)}.alertify .alert>*,.alertify .dialog>*{width:400px;max-width:95%;margin:0 auto;text-align:center;padding:12px;background:#fff;box-shadow:0 2px 4px -1px rgba(0,0,0,.14),0 4px 5px 0 rgba(0,0,0,.098),0 1px 10px 0 rgba(0,0,0,.084)}.alertify .alert .msg,.alertify .dialog .msg{padding:12px;margin-bottom:12px;margin:0;text-align:left}.alertify .alert input:not(.form-control),.alertify .dialog input:not(.form-control){margin-bottom:15px;width:100%;font-size:100%;padding:12px}.alertify .alert input:not(.form-control):focus,.alertify .dialog input:not(.form-control):focus{outline-offset:-2px}.alertify .alert nav,.alertify .dialog nav{text-align:right}.alertify .alert nav button:not(.btn):not(.pure-button):not(.md-button):not(.mdl-button),.alertify .dialog nav button:not(.btn):not(.pure-button):not(.md-button):not(.mdl-button){background:transparent;box-sizing:border-box;color:rgba(0,0,0,.87);position:relative;outline:0;border:0;display:inline-block;-webkit-align-items:center;-ms-flex-align:center;-ms-grid-row-align:center;align-items:center;padding:0 6px;margin:6px 8px;line-height:36px;min-height:36px;white-space:nowrap;min-width:88px;text-align:center;text-transform:uppercase;font-size:14px;text-decoration:none;cursor:pointer;border:1px solid transparent;border-radius:2px}.alertify .alert nav button:not(.btn):not(.pure-button):not(.md-button):not(.mdl-button):active,.alertify .alert nav button:not(.btn):not(.pure-button):not(.md-button):not(.mdl-button):hover,.alertify .dialog nav button:not(.btn):not(.pure-button):not(.md-button):not(.mdl-button):active,.alertify .dialog nav button:not(.btn):not(.pure-button):not(.md-button):not(.mdl-button):hover{background-color:rgba(0,0,0,.05)}.alertify .alert nav button:not(.btn):not(.pure-button):not(.md-button):not(.mdl-button):focus,.alertify .dialog nav button:not(.btn):not(.pure-button):not(.md-button):not(.mdl-button):focus{border:1px solid rgba(0,0,0,.1)}.alertify .alert nav button.btn,.alertify .dialog nav button.btn{margin:6px 4px}.alertify-logs{position:fixed;z-index:1}.alertify-logs.bottom,.alertify-logs:not(.top){bottom:16px}.alertify-logs.left,.alertify-logs:not(.right){left:16px}.alertify-logs.left>*,.alertify-logs:not(.right)>*{float:left;-webkit-transform:translateZ(0);transform:translateZ(0);height:auto}.alertify-logs.left>.show,.alertify-logs:not(.right)>.show{left:0}.alertify-logs.left>*,.alertify-logs.left>.hide,.alertify-logs:not(.right)>*,.alertify-logs:not(.right)>.hide{left:-110%}.alertify-logs.right{right:16px}.alertify-logs.right>*{float:right;-webkit-transform:translateZ(0);transform:translateZ(0)}.alertify-logs.right>.show{right:0;opacity:1}.alertify-logs.right>*,.alertify-logs.right>.hide{right:-110%;opacity:0}.alertify-logs.top{top:0}.alertify-logs>*{box-sizing:border-box;transition:all .4s cubic-bezier(.25,.8,.25,1);position:relative;clear:both;-webkit-backface-visibility:hidden;backface-visibility:hidden;-webkit-perspective:1000;perspective:1000;max-height:0;margin:0;padding:0;overflow:hidden;opacity:0;pointer-events:none}.alertify-logs>.show{margin-top:12px;opacity:1;max-height:1000px;padding:12px;pointer-events:auto}",t.insertBefore(e,t.firstChild)}},removeCSS:function(){var t=document.querySelector("#alertifyCSS");t&&t.parentNode&&t.parentNode.removeChild(t)}};return t.injectCSS(),{_$$alertify:t,reset:function(){return t.reset(),this},alert:function(e,o,n){return t.dialog(e,"alert",o,n)||this},confirm:function(e,o,n){return t.dialog(e,"confirm",o,n)||this},prompt:function(e,o,n){return t.dialog(e,"prompt",o,n)||this},log:function(e,o){return t.log(e,"default",o),this},theme:function(e){return t.theme(e),this},success:function(e,o){return t.log(e,"success",o),this},error:function(e,o){return t.log(e,"error",o),this},cancelBtn:function(e){return t.cancelBtn(e),this},okBtn:function(e){return t.okBtn(e),this},delay:function(e){return t.setDelay(e),this},placeholder:function(e){return t.promptPlaceholder=e,this},defaultValue:function(e){return t.promptValue=e,this},maxLogItems:function(e){return t.setMaxLogItems(e),this},closeLogOnClick:function(e){return t.setCloseLogOnClick(!!e),this},logPosition:function(e){return t.setLogPosition(e||""),this},setLogTemplate:function(e){return t.logTemplateMethod=e,this},clearLogs:function(){return t.setupLogContainer().innerHTML="",this},version:t.version}}var e=500,o=function(t){if(t){var o=function(){t&&t.parentNode&&t.parentNode.removeChild(t)};t.classList.remove("show"),t.classList.add("hide"),t.addEventListener("transitionend",o),setTimeout(o,e)}};if("undefined"!=typeof module&&module&&module.exports){module.exports=function(){return new t};var n=new t;for(var i in n)module.exports[i]=n[i]}else"function"==typeof define&&define.amd?define(function(){return new t}):window.alertify=new t}(); \ No newline at end of file diff --git a/public/assets/js/fetch.js b/public/assets/js/fetch.js new file mode 100644 index 00000000..fac11e42 --- /dev/null +++ b/public/assets/js/fetch.js @@ -0,0 +1,389 @@ +(function(self) { + 'use strict'; + + if (self.fetch) { + return + } + + function normalizeName(name) { + if (typeof name !== 'string') { + name = String(name) + } + if (/[^a-z0-9\-#$%&'*+.\^_`|~]/i.test(name)) { + throw new TypeError('Invalid character in header field name') + } + return name.toLowerCase() + } + + function normalizeValue(value) { + if (typeof value !== 'string') { + value = String(value) + } + return value + } + + function Headers(headers) { + this.map = {} + + if (headers instanceof Headers) { + headers.forEach(function(value, name) { + this.append(name, value) + }, this) + + } else if (headers) { + Object.getOwnPropertyNames(headers).forEach(function(name) { + this.append(name, headers[name]) + }, this) + } + } + + Headers.prototype.append = function(name, value) { + name = normalizeName(name) + value = normalizeValue(value) + var list = this.map[name] + if (!list) { + list = [] + this.map[name] = list + } + list.push(value) + } + + Headers.prototype['delete'] = function(name) { + delete this.map[normalizeName(name)] + } + + Headers.prototype.get = function(name) { + var values = this.map[normalizeName(name)] + return values ? values[0] : null + } + + Headers.prototype.getAll = function(name) { + return this.map[normalizeName(name)] || [] + } + + Headers.prototype.has = function(name) { + return this.map.hasOwnProperty(normalizeName(name)) + } + + Headers.prototype.set = function(name, value) { + this.map[normalizeName(name)] = [normalizeValue(value)] + } + + Headers.prototype.forEach = function(callback, thisArg) { + Object.getOwnPropertyNames(this.map).forEach(function(name) { + this.map[name].forEach(function(value) { + callback.call(thisArg, value, name, this) + }, this) + }, this) + } + + function consumed(body) { + if (body.bodyUsed) { + return Promise.reject(new TypeError('Already read')) + } + body.bodyUsed = true + } + + function fileReaderReady(reader) { + return new Promise(function(resolve, reject) { + reader.onload = function() { + resolve(reader.result) + } + reader.onerror = function() { + reject(reader.error) + } + }) + } + + function readBlobAsArrayBuffer(blob) { + var reader = new FileReader() + reader.readAsArrayBuffer(blob) + return fileReaderReady(reader) + } + + function readBlobAsText(blob) { + var reader = new FileReader() + reader.readAsText(blob) + return fileReaderReady(reader) + } + + var support = { + blob: 'FileReader' in self && 'Blob' in self && (function() { + try { + new Blob(); + return true + } catch(e) { + return false + } + })(), + formData: 'FormData' in self, + arrayBuffer: 'ArrayBuffer' in self + } + + function Body() { + this.bodyUsed = false + + + this._initBody = function(body) { + this._bodyInit = body + if (typeof body === 'string') { + this._bodyText = body + } else if (support.blob && Blob.prototype.isPrototypeOf(body)) { + this._bodyBlob = body + } else if (support.formData && FormData.prototype.isPrototypeOf(body)) { + this._bodyFormData = body + } else if (!body) { + this._bodyText = '' + } else if (support.arrayBuffer && ArrayBuffer.prototype.isPrototypeOf(body)) { + // Only support ArrayBuffers for POST method. + // Receiving ArrayBuffers happens via Blobs, instead. + } else { + throw new Error('unsupported BodyInit type') + } + + if (!this.headers.get('content-type')) { + if (typeof body === 'string') { + this.headers.set('content-type', 'text/plain;charset=UTF-8') + } else if (this._bodyBlob && this._bodyBlob.type) { + this.headers.set('content-type', this._bodyBlob.type) + } + } + } + + if (support.blob) { + this.blob = function() { + var rejected = consumed(this) + if (rejected) { + return rejected + } + + if (this._bodyBlob) { + return Promise.resolve(this._bodyBlob) + } else if (this._bodyFormData) { + throw new Error('could not read FormData body as blob') + } else { + return Promise.resolve(new Blob([this._bodyText])) + } + } + + this.arrayBuffer = function() { + return this.blob().then(readBlobAsArrayBuffer) + } + + this.text = function() { + var rejected = consumed(this) + if (rejected) { + return rejected + } + + if (this._bodyBlob) { + return readBlobAsText(this._bodyBlob) + } else if (this._bodyFormData) { + throw new Error('could not read FormData body as text') + } else { + return Promise.resolve(this._bodyText) + } + } + } else { + this.text = function() { + var rejected = consumed(this) + return rejected ? rejected : Promise.resolve(this._bodyText) + } + } + + if (support.formData) { + this.formData = function() { + return this.text().then(decode) + } + } + + this.json = function() { + return this.text().then(JSON.parse) + } + + return this + } + + // HTTP methods whose capitalization should be normalized + var methods = ['DELETE', 'GET', 'HEAD', 'OPTIONS', 'POST', 'PUT'] + + function normalizeMethod(method) { + var upcased = method.toUpperCase() + return (methods.indexOf(upcased) > -1) ? upcased : method + } + + function Request(input, options) { + options = options || {} + var body = options.body + if (Request.prototype.isPrototypeOf(input)) { + if (input.bodyUsed) { + throw new TypeError('Already read') + } + this.url = input.url + this.credentials = input.credentials + if (!options.headers) { + this.headers = new Headers(input.headers) + } + this.method = input.method + this.mode = input.mode + if (!body) { + body = input._bodyInit + input.bodyUsed = true + } + } else { + this.url = input + } + + this.credentials = options.credentials || this.credentials || 'omit' + if (options.headers || !this.headers) { + this.headers = new Headers(options.headers) + } + this.method = normalizeMethod(options.method || this.method || 'GET') + this.mode = options.mode || this.mode || null + this.referrer = null + + if ((this.method === 'GET' || this.method === 'HEAD') && body) { + throw new TypeError('Body not allowed for GET or HEAD requests') + } + this._initBody(body) + } + + Request.prototype.clone = function() { + return new Request(this) + } + + function decode(body) { + var form = new FormData() + body.trim().split('&').forEach(function(bytes) { + if (bytes) { + var split = bytes.split('=') + var name = split.shift().replace(/\+/g, ' ') + var value = split.join('=').replace(/\+/g, ' ') + form.append(decodeURIComponent(name), decodeURIComponent(value)) + } + }) + return form + } + + function headers(xhr) { + var head = new Headers() + var pairs = xhr.getAllResponseHeaders().trim().split('\n') + pairs.forEach(function(header) { + var split = header.trim().split(':') + var key = split.shift().trim() + var value = split.join(':').trim() + head.append(key, value) + }) + return head + } + + Body.call(Request.prototype) + + function Response(bodyInit, options) { + if (!options) { + options = {} + } + + this.type = 'default' + this.status = options.status + this.ok = this.status >= 200 && this.status < 300 + this.statusText = options.statusText + this.headers = options.headers instanceof Headers ? options.headers : new Headers(options.headers) + this.url = options.url || '' + this._initBody(bodyInit) + } + + Body.call(Response.prototype) + + Response.prototype.clone = function() { + return new Response(this._bodyInit, { + status: this.status, + statusText: this.statusText, + headers: new Headers(this.headers), + url: this.url + }) + } + + Response.error = function() { + var response = new Response(null, {status: 0, statusText: ''}) + response.type = 'error' + return response + } + + var redirectStatuses = [301, 302, 303, 307, 308] + + Response.redirect = function(url, status) { + if (redirectStatuses.indexOf(status) === -1) { + throw new RangeError('Invalid status code') + } + + return new Response(null, {status: status, headers: {location: url}}) + } + + self.Headers = Headers; + self.Request = Request; + self.Response = Response; + + self.fetch = function(input, init) { + return new Promise(function(resolve, reject) { + var request + if (Request.prototype.isPrototypeOf(input) && !init) { + request = input + } else { + request = new Request(input, init) + } + + var xhr = new XMLHttpRequest() + + function responseURL() { + if ('responseURL' in xhr) { + return xhr.responseURL + } + + // Avoid security warnings on getResponseHeader when not allowed by CORS + if (/^X-Request-URL:/m.test(xhr.getAllResponseHeaders())) { + return xhr.getResponseHeader('X-Request-URL') + } + + return; + } + + xhr.onload = function() { + var status = (xhr.status === 1223) ? 204 : xhr.status + if (status < 100 || status > 599) { + reject(new TypeError('Network request failed')) + return + } + var options = { + status: status, + statusText: xhr.statusText, + headers: headers(xhr), + url: responseURL() + } + var body = 'response' in xhr ? xhr.response : xhr.responseText; + resolve(new Response(body, options)) + } + + xhr.onerror = function() { + reject(new TypeError('Network request failed')) + } + + xhr.open(request.method, request.url, true) + + if (request.credentials === 'include') { + xhr.withCredentials = true + } + + if ('responseType' in xhr && support.blob) { + xhr.responseType = 'blob' + } + + request.headers.forEach(function(value, name) { + xhr.setRequestHeader(name, value) + }) + + xhr.send(typeof request._bodyInit === 'undefined' ? null : request._bodyInit) + }) + } + self.fetch.polyfill = true +})(typeof self !== 'undefined' ? self : this); diff --git a/public/assets/js/form-save.js b/public/assets/js/form-save.js new file mode 100644 index 00000000..151d1219 --- /dev/null +++ b/public/assets/js/form-save.js @@ -0,0 +1,69 @@ +var feature = { + addEventListener : !!window.addEventListener, + querySelectorAll : !!document.querySelectorAll, +}; +if(feature.addEventListener && feature.querySelectorAll) { + this.init(); +} +function init() { + var keys = getKeys(); + for(var i = 0; i < keys.length; i++) { + if(store.get(keys[i])) { + var formId = keys[i].split("~")[1]; + document.getElementById(formId).value = store.get(keys[i]); + } + } +} +var timerId = window.setInterval(function() { + var saved = false; + var inputs = document.querySelectorAll('input[type=text], textarea'); + for(var i = 0; i < inputs.length; i++) { + var key = getFormElement(inputs[i]).id + '~' + inputs[i].id; + if(store.get(key) !== inputs[i].value && inputs[i].value !== "") { + store.set(key, inputs[i].value); + saved = true; + } + } + if(saved === true) { + alertify.logPosition('top right'); + alertify.success('Auto saved text'); + } +}, 5000); +var forms = document.querySelectorAll('form'); +for(var f = 0; f < forms.length; f++) { + var form = forms[f]; + form.addEventListener('submit', function() { + window.clearInterval(timerId); + var formId = form.id; + var storedKeys = store.keys(); + for(var i = 0; i < storedKeys.length; i++) { + if(storedKeys[i].indexOf(formId) > -1) { + store.remove(storedKeys[i]); + } + } + }); +} +function getKeys() { + var keys = []; + var formFields = document.querySelectorAll('input[type=text], textarea'); + for(var f = 0; f < formFields.length; f++) { + var parent = getFormElement(formFields[f]); + if(parent !== false) { + var key = parent.id + '~' + formFields[f].id; + keys.push(key); + } + } + return keys; +} +function getFormElement(elem) { + if(elem.nodeName.toLowerCase() !== 'body') { + var parent = elem.parentNode; + if(parent.nodeName.toLowerCase() === 'form') { + return parent; + } else { + return getFormElement(parent); + } + } else { + return false; + } +} diff --git a/public/assets/js/links.js b/public/assets/js/links.js new file mode 100644 index 00000000..b683edd5 --- /dev/null +++ b/public/assets/js/links.js @@ -0,0 +1,25 @@ +//the autlinker object +var autolinker = new Autolinker(); + +//the youtube regex +var ytidregex = /watch\?v=([A-Za-z0-9\-_]+)/; + +//grab the notes and loop through them +var notes = document.querySelectorAll('.e-content'); +for(var i = 0; i < notes.length; i++) { + //get Youtube ID + var ytid = notes[i].textContent.match(ytidregex); + if(ytid !== null) { + var id = ytid[1]; + var iframe = document.createElement('iframe'); + iframe.classList.add('youtube'); + iframe.setAttribute('src', '//www.youtube.com/embed/' + id); + iframe.setAttribute('frameborder', 0); + iframe.setAttribute('allowfullscreen', 'true'); + notes[i].appendChild(iframe); + } + //now linkify everything + var orig = notes[i].innerHTML; + var linked = autolinker.link(orig); + notes[i].innerHTML = linked; +} \ No newline at end of file diff --git a/public/assets/js/maps.js b/public/assets/js/maps.js new file mode 100644 index 00000000..4901c5da --- /dev/null +++ b/public/assets/js/maps.js @@ -0,0 +1,15 @@ +//This code runs on page load and looks for
, then adds map +var mapDivs = document.querySelectorAll('.map'); +for(var i = 0; i < mapDivs.length; i++) { + var mapDiv = mapDivs[i]; + var latitude = mapDiv.dataset.latitude; + var longitude = mapDiv.dataset.longitude; + L.mapbox.accessToken = 'pk.eyJ1Ijoiam9ubnliYXJuZXMiLCJhIjoiVlpndW1EYyJ9.aP9fxAqLKh7lj0LpFh5k1w'; + var map = L.mapbox.map(mapDiv, 'jonnybarnes.gnoihnim') + .setView([latitude, longitude], 15) + .addLayer(L.mapbox.tileLayer('jonnybarnes.gnoihnim', { + detectRetina: true, + })); + var marker = L.marker([latitude, longitude]).addTo(map); + map.scrollWheelZoom.disable(); +} diff --git a/public/assets/js/marked.min.js b/public/assets/js/marked.min.js new file mode 100644 index 00000000..555c1dc1 --- /dev/null +++ b/public/assets/js/marked.min.js @@ -0,0 +1,6 @@ +/** + * marked - a markdown parser + * Copyright (c) 2011-2014, Christopher Jeffrey. (MIT Licensed) + * https://github.com/chjj/marked + */ +(function(){var block={newline:/^\n+/,code:/^( {4}[^\n]+\n*)+/,fences:noop,hr:/^( *[-*_]){3,} *(?:\n+|$)/,heading:/^ *(#{1,6}) *([^\n]+?) *#* *(?:\n+|$)/,nptable:noop,lheading:/^([^\n]+)\n *(=|-){2,} *(?:\n+|$)/,blockquote:/^( *>[^\n]+(\n(?!def)[^\n]+)*\n*)+/,list:/^( *)(bull) [\s\S]+?(?:hr|def|\n{2,}(?! )(?!\1bull )\n*|\s*$)/,html:/^ *(?:comment *(?:\n|\s*$)|closed *(?:\n{2,}|\s*$)|closing *(?:\n{2,}|\s*$))/,def:/^ *\[([^\]]+)\]: *]+)>?(?: +["(]([^\n]+)[")])? *(?:\n+|$)/,table:noop,paragraph:/^((?:[^\n]+\n?(?!hr|heading|lheading|blockquote|tag|def))+)\n*/,text:/^[^\n]+/};block.bullet=/(?:[*+-]|\d+\.)/;block.item=/^( *)(bull) [^\n]*(?:\n(?!\1bull )[^\n]*)*/;block.item=replace(block.item,"gm")(/bull/g,block.bullet)();block.list=replace(block.list)(/bull/g,block.bullet)("hr","\\n+(?=\\1?(?:[-*_] *){3,}(?:\\n+|$))")("def","\\n+(?="+block.def.source+")")();block.blockquote=replace(block.blockquote)("def",block.def)();block._tag="(?!(?:"+"a|em|strong|small|s|cite|q|dfn|abbr|data|time|code"+"|var|samp|kbd|sub|sup|i|b|u|mark|ruby|rt|rp|bdi|bdo"+"|span|br|wbr|ins|del|img)\\b)\\w+(?!:/|[^\\w\\s@]*@)\\b";block.html=replace(block.html)("comment",//)("closed",/<(tag)[\s\S]+?<\/\1>/)("closing",/])*?>/)(/tag/g,block._tag)();block.paragraph=replace(block.paragraph)("hr",block.hr)("heading",block.heading)("lheading",block.lheading)("blockquote",block.blockquote)("tag","<"+block._tag)("def",block.def)();block.normal=merge({},block);block.gfm=merge({},block.normal,{fences:/^ *(`{3,}|~{3,})[ \.]*(\S+)? *\n([\s\S]*?)\s*\1 *(?:\n+|$)/,paragraph:/^/,heading:/^ *(#{1,6}) +([^\n]+?) *#* *(?:\n+|$)/});block.gfm.paragraph=replace(block.paragraph)("(?!","(?!"+block.gfm.fences.source.replace("\\1","\\2")+"|"+block.list.source.replace("\\1","\\3")+"|")();block.tables=merge({},block.gfm,{nptable:/^ *(\S.*\|.*)\n *([-:]+ *\|[-| :]*)\n((?:.*\|.*(?:\n|$))*)\n*/,table:/^ *\|(.+)\n *\|( *[-:]+[-| :]*)\n((?: *\|.*(?:\n|$))*)\n*/});function Lexer(options){this.tokens=[];this.tokens.links={};this.options=options||marked.defaults;this.rules=block.normal;if(this.options.gfm){if(this.options.tables){this.rules=block.tables}else{this.rules=block.gfm}}}Lexer.rules=block;Lexer.lex=function(src,options){var lexer=new Lexer(options);return lexer.lex(src)};Lexer.prototype.lex=function(src){src=src.replace(/\r\n|\r/g,"\n").replace(/\t/g," ").replace(/\u00a0/g," ").replace(/\u2424/g,"\n");return this.token(src,true)};Lexer.prototype.token=function(src,top,bq){var src=src.replace(/^ +$/gm,""),next,loose,cap,bull,b,item,space,i,l;while(src){if(cap=this.rules.newline.exec(src)){src=src.substring(cap[0].length);if(cap[0].length>1){this.tokens.push({type:"space"})}}if(cap=this.rules.code.exec(src)){src=src.substring(cap[0].length);cap=cap[0].replace(/^ {4}/gm,"");this.tokens.push({type:"code",text:!this.options.pedantic?cap.replace(/\n+$/,""):cap});continue}if(cap=this.rules.fences.exec(src)){src=src.substring(cap[0].length);this.tokens.push({type:"code",lang:cap[2],text:cap[3]||""});continue}if(cap=this.rules.heading.exec(src)){src=src.substring(cap[0].length);this.tokens.push({type:"heading",depth:cap[1].length,text:cap[2]});continue}if(top&&(cap=this.rules.nptable.exec(src))){src=src.substring(cap[0].length);item={type:"table",header:cap[1].replace(/^ *| *\| *$/g,"").split(/ *\| */),align:cap[2].replace(/^ *|\| *$/g,"").split(/ *\| */),cells:cap[3].replace(/\n$/,"").split("\n")};for(i=0;i ?/gm,"");this.token(cap,top,true);this.tokens.push({type:"blockquote_end"});continue}if(cap=this.rules.list.exec(src)){src=src.substring(cap[0].length);bull=cap[2];this.tokens.push({type:"list_start",ordered:bull.length>1});cap=cap[0].match(this.rules.item);next=false;l=cap.length;i=0;for(;i1&&b.length>1)){src=cap.slice(i+1).join("\n")+src;i=l-1}}loose=next||/\n\n(?!\s*$)/.test(item);if(i!==l-1){next=item.charAt(item.length-1)==="\n";if(!loose)loose=next}this.tokens.push({type:loose?"loose_item_start":"list_item_start"});this.token(item,false,bq);this.tokens.push({type:"list_item_end"})}this.tokens.push({type:"list_end"});continue}if(cap=this.rules.html.exec(src)){src=src.substring(cap[0].length);this.tokens.push({type:this.options.sanitize?"paragraph":"html",pre:!this.options.sanitizer&&(cap[1]==="pre"||cap[1]==="script"||cap[1]==="style"),text:cap[0]});continue}if(!bq&&top&&(cap=this.rules.def.exec(src))){src=src.substring(cap[0].length);this.tokens.links[cap[1].toLowerCase()]={href:cap[2],title:cap[3]};continue}if(top&&(cap=this.rules.table.exec(src))){src=src.substring(cap[0].length);item={type:"table",header:cap[1].replace(/^ *| *\| *$/g,"").split(/ *\| */),align:cap[2].replace(/^ *|\| *$/g,"").split(/ *\| */),cells:cap[3].replace(/(?: *\| *)?\n$/,"").split("\n")};for(i=0;i])/,autolink:/^<([^ >]+(@|:\/)[^ >]+)>/,url:noop,tag:/^|^<\/?\w+(?:"[^"]*"|'[^']*'|[^'">])*?>/,link:/^!?\[(inside)\]\(href\)/,reflink:/^!?\[(inside)\]\s*\[([^\]]*)\]/,nolink:/^!?\[((?:\[[^\]]*\]|[^\[\]])*)\]/,strong:/^__([\s\S]+?)__(?!_)|^\*\*([\s\S]+?)\*\*(?!\*)/,em:/^\b_((?:[^_]|__)+?)_\b|^\*((?:\*\*|[\s\S])+?)\*(?!\*)/,code:/^(`+)\s*([\s\S]*?[^`])\s*\1(?!`)/,br:/^ {2,}\n(?!\s*$)/,del:noop,text:/^[\s\S]+?(?=[\\?(?:\s+['"]([\s\S]*?)['"])?\s*/;inline.link=replace(inline.link)("inside",inline._inside)("href",inline._href)();inline.reflink=replace(inline.reflink)("inside",inline._inside)();inline.normal=merge({},inline);inline.pedantic=merge({},inline.normal,{strong:/^__(?=\S)([\s\S]*?\S)__(?!_)|^\*\*(?=\S)([\s\S]*?\S)\*\*(?!\*)/,em:/^_(?=\S)([\s\S]*?\S)_(?!_)|^\*(?=\S)([\s\S]*?\S)\*(?!\*)/});inline.gfm=merge({},inline.normal,{escape:replace(inline.escape)("])","~|])")(),url:/^(https?:\/\/[^\s<]+[^<.,:;"')\]\s])/,del:/^~~(?=\S)([\s\S]*?\S)~~/,text:replace(inline.text)("]|","~]|")("|","|https?://|")()});inline.breaks=merge({},inline.gfm,{br:replace(inline.br)("{2,}","*")(),text:replace(inline.gfm.text)("{2,}","*")()});function InlineLexer(links,options){this.options=options||marked.defaults;this.links=links;this.rules=inline.normal;this.renderer=this.options.renderer||new Renderer;this.renderer.options=this.options;if(!this.links){throw new Error("Tokens array requires a `links` property.")}if(this.options.gfm){if(this.options.breaks){this.rules=inline.breaks}else{this.rules=inline.gfm}}else if(this.options.pedantic){this.rules=inline.pedantic}}InlineLexer.rules=inline;InlineLexer.output=function(src,links,options){var inline=new InlineLexer(links,options);return inline.output(src)};InlineLexer.prototype.output=function(src){var out="",link,text,href,cap;while(src){if(cap=this.rules.escape.exec(src)){src=src.substring(cap[0].length);out+=cap[1];continue}if(cap=this.rules.autolink.exec(src)){src=src.substring(cap[0].length);if(cap[2]==="@"){text=cap[1].charAt(6)===":"?this.mangle(cap[1].substring(7)):this.mangle(cap[1]);href=this.mangle("mailto:")+text}else{text=escape(cap[1]);href=text}out+=this.renderer.link(href,null,text);continue}if(!this.inLink&&(cap=this.rules.url.exec(src))){src=src.substring(cap[0].length);text=escape(cap[1]);href=text;out+=this.renderer.link(href,null,text);continue}if(cap=this.rules.tag.exec(src)){if(!this.inLink&&/^/i.test(cap[0])){this.inLink=false}src=src.substring(cap[0].length);out+=this.options.sanitize?this.options.sanitizer?this.options.sanitizer(cap[0]):escape(cap[0]):cap[0];continue}if(cap=this.rules.link.exec(src)){src=src.substring(cap[0].length);this.inLink=true;out+=this.outputLink(cap,{href:cap[2],title:cap[3]});this.inLink=false;continue}if((cap=this.rules.reflink.exec(src))||(cap=this.rules.nolink.exec(src))){src=src.substring(cap[0].length);link=(cap[2]||cap[1]).replace(/\s+/g," ");link=this.links[link.toLowerCase()];if(!link||!link.href){out+=cap[0].charAt(0);src=cap[0].substring(1)+src;continue}this.inLink=true;out+=this.outputLink(cap,link);this.inLink=false;continue}if(cap=this.rules.strong.exec(src)){src=src.substring(cap[0].length);out+=this.renderer.strong(this.output(cap[2]||cap[1]));continue}if(cap=this.rules.em.exec(src)){src=src.substring(cap[0].length);out+=this.renderer.em(this.output(cap[2]||cap[1]));continue}if(cap=this.rules.code.exec(src)){src=src.substring(cap[0].length);out+=this.renderer.codespan(escape(cap[2],true));continue}if(cap=this.rules.br.exec(src)){src=src.substring(cap[0].length);out+=this.renderer.br();continue}if(cap=this.rules.del.exec(src)){src=src.substring(cap[0].length);out+=this.renderer.del(this.output(cap[1]));continue}if(cap=this.rules.text.exec(src)){src=src.substring(cap[0].length);out+=this.renderer.text(escape(this.smartypants(cap[0])));continue}if(src){throw new Error("Infinite loop on byte: "+src.charCodeAt(0))}}return out};InlineLexer.prototype.outputLink=function(cap,link){var href=escape(link.href),title=link.title?escape(link.title):null;return cap[0].charAt(0)!=="!"?this.renderer.link(href,title,this.output(cap[1])):this.renderer.image(href,title,escape(cap[1]))};InlineLexer.prototype.smartypants=function(text){if(!this.options.smartypants)return text;return text.replace(/---/g,"—").replace(/--/g,"–").replace(/(^|[-\u2014/(\[{"\s])'/g,"$1‘").replace(/'/g,"’").replace(/(^|[-\u2014/(\[{\u2018\s])"/g,"$1“").replace(/"/g,"”").replace(/\.{3}/g,"…")};InlineLexer.prototype.mangle=function(text){if(!this.options.mangle)return text;var out="",l=text.length,i=0,ch;for(;i.5){ch="x"+ch.toString(16)}out+="&#"+ch+";"}return out};function Renderer(options){this.options=options||{}}Renderer.prototype.code=function(code,lang,escaped){if(this.options.highlight){var out=this.options.highlight(code,lang);if(out!=null&&out!==code){escaped=true;code=out}}if(!lang){return"
"+(escaped?code:escape(code,true))+"\n
"}return'
'+(escaped?code:escape(code,true))+"\n
\n"};Renderer.prototype.blockquote=function(quote){return"
\n"+quote+"
\n"};Renderer.prototype.html=function(html){return html};Renderer.prototype.heading=function(text,level,raw){return"'+text+"\n"};Renderer.prototype.hr=function(){return this.options.xhtml?"
\n":"
\n"};Renderer.prototype.list=function(body,ordered){var type=ordered?"ol":"ul";return"<"+type+">\n"+body+"\n"};Renderer.prototype.listitem=function(text){return"
  • "+text+"
  • \n"};Renderer.prototype.paragraph=function(text){return"

    "+text+"

    \n"};Renderer.prototype.table=function(header,body){return"\n"+"\n"+header+"\n"+"\n"+body+"\n"+"
    \n"};Renderer.prototype.tablerow=function(content){return"\n"+content+"\n"};Renderer.prototype.tablecell=function(content,flags){var type=flags.header?"th":"td";var tag=flags.align?"<"+type+' style="text-align:'+flags.align+'">':"<"+type+">";return tag+content+"\n"};Renderer.prototype.strong=function(text){return""+text+""};Renderer.prototype.em=function(text){return""+text+""};Renderer.prototype.codespan=function(text){return""+text+""};Renderer.prototype.br=function(){return this.options.xhtml?"
    ":"
    "};Renderer.prototype.del=function(text){return""+text+""};Renderer.prototype.link=function(href,title,text){if(this.options.sanitize){try{var prot=decodeURIComponent(unescape(href)).replace(/[^\w:]/g,"").toLowerCase()}catch(e){return""}if(prot.indexOf("javascript:")===0||prot.indexOf("vbscript:")===0){return""}}var out='
    ";return out};Renderer.prototype.image=function(href,title,text){var out=''+text+'":">";return out};Renderer.prototype.text=function(text){return text};function Parser(options){this.tokens=[];this.token=null;this.options=options||marked.defaults;this.options.renderer=this.options.renderer||new Renderer;this.renderer=this.options.renderer;this.renderer.options=this.options}Parser.parse=function(src,options,renderer){var parser=new Parser(options,renderer);return parser.parse(src)};Parser.prototype.parse=function(src){this.inline=new InlineLexer(src.links,this.options,this.renderer);this.tokens=src.reverse();var out="";while(this.next()){out+=this.tok()}return out};Parser.prototype.next=function(){return this.token=this.tokens.pop()};Parser.prototype.peek=function(){return this.tokens[this.tokens.length-1]||0};Parser.prototype.parseText=function(){var body=this.token.text;while(this.peek().type==="text"){body+="\n"+this.next().text}return this.inline.output(body)};Parser.prototype.tok=function(){switch(this.token.type){case"space":{return""}case"hr":{return this.renderer.hr()}case"heading":{return this.renderer.heading(this.inline.output(this.token.text),this.token.depth,this.token.text)}case"code":{return this.renderer.code(this.token.text,this.token.lang,this.token.escaped)}case"table":{var header="",body="",i,row,cell,flags,j;cell="";for(i=0;i/g,">").replace(/"/g,""").replace(/'/g,"'")}function unescape(html){return html.replace(/&([#\w]+);/g,function(_,n){n=n.toLowerCase();if(n==="colon")return":";if(n.charAt(0)==="#"){return n.charAt(1)==="x"?String.fromCharCode(parseInt(n.substring(2),16)):String.fromCharCode(+n.substring(1))}return""})}function replace(regex,opt){regex=regex.source;opt=opt||"";return function self(name,val){if(!name)return new RegExp(regex,opt);val=val.source||val;val=val.replace(/(^|[^\[])\^/g,"$1");regex=regex.replace(name,val);return self}}function noop(){}noop.exec=noop;function merge(obj){var i=1,target,key;for(;iAn error occured:

    "+escape(e.message+"",true)+"
    "}throw e}}marked.options=marked.setOptions=function(opt){merge(marked.defaults,opt);return marked};marked.defaults={gfm:true,tables:true,breaks:false,pedantic:false,sanitize:false,sanitizer:null,mangle:true,smartLists:false,silent:false,highlight:null,langPrefix:"lang-",smartypants:false,headerPrefix:"",renderer:new Renderer,xhtml:false};marked.Parser=Parser;marked.parser=Parser.parse;marked.Renderer=Renderer;marked.Lexer=Lexer;marked.lexer=Lexer.lex;marked.InlineLexer=InlineLexer;marked.inlineLexer=InlineLexer.output;marked.parse=marked;if(typeof module!=="undefined"&&typeof exports==="object"){module.exports=marked}else if(typeof define==="function"&&define.amd){define(function(){return marked})}else{this.marked=marked}}).call(function(){return this||(typeof window!=="undefined"?window:global)}()); \ No newline at end of file diff --git a/public/assets/js/newnote.js b/public/assets/js/newnote.js new file mode 100644 index 00000000..2a26b261 --- /dev/null +++ b/public/assets/js/newnote.js @@ -0,0 +1,284 @@ +if ('geolocation' in navigator) { + var button = document.querySelector('#locate'); + if (button.addEventListener) { + //if we have javascript, event listeners and geolocation, make the locate + //button clickable and add event + button.disabled = false; + button.addEventListener('click', getLocation); + } +} + +function getLocation() { + navigator.geolocation.getCurrentPosition(function (position) { + //the locate button has been clicked so add the places/map + addPlaces(position.coords.latitude, position.coords.longitude); + }); +} + +function addPlaces(latitude, longitude) { + //get the nearby places + fetch('/places/near/' + latitude + '/' + longitude, { + credentials: 'same-origin', + method: 'get' + }).then(function (response) { + return response.json(); + }).then(function (j) { + if (j.length > 0) { + var i; + var places = []; + for (i = 0; i < j.length; ++i) { + var latlng = parseLocation(j[i].location); + var name = j[i].name; + var slug = j[i].slug; + places.push([name, slug, latlng[0], latlng[1]]); + } + //add a map with the nearby places + addMap(latitude, longitude, places); + } else { + //add a map with just current location + addMap(latitude, longitude); + } + }).catch(function (err) { + console.log(err); + }); +} + +function addMap(latitude, longitude, places) { + //make places null if not supplied + if (arguments.length == 2) { + places = null; + } + var form = button.parentNode; + var div = document.createElement('div'); + div.setAttribute('id', 'map'); + //add the map div + form.appendChild(div); + L.mapbox.accessToken = 'pk.eyJ1Ijoiam9ubnliYXJuZXMiLCJhIjoiVlpndW1EYyJ9.aP9fxAqLKh7lj0LpFh5k1w'; + var map = L.mapbox.map('map', 'jonnybarnes.gnoihnim') + .setView([latitude, longitude], 15) + .addLayer(L.mapbox.tileLayer('jonnybarnes.gnoihnim', { + detectRetina: true, + })); + //add a marker for the current location + var marker = L.marker([latitude, longitude], { + draggable: true, + }).addTo(map); + //when the location marker is dragged, if the new place form elements exist + //update the lat/lng values + marker.on('dragend', function () { + var placeFormLatitude = document.querySelector('#place-latitude'); + if (placeFormLatitude !== null) { + placeFormLatitude.value = getLatitudeFromMapboxMarker(marker.getLatLng()); + } + var placeFormLongitude = document.querySelector('#place-longitude'); + if (placeFormLongitude !== null) { + placeFormLongitude.value = getLongitudeFromMapboxMarker(marker.getLatLng()); + } + }); + //create the + places.forEach(function (item, index, array) { + var option = document.createElement('option'); + option.setAttribute('value', item[1]); + var text = document.createTextNode(item[0]); + option.appendChild(text); + option.dataset.latitude = item[2]; + option.dataset.longitude = item[3]; + selectEl.appendChild(option); + var placeMarker = L.marker([item[2], item[3]], { + icon: L.mapbox.marker.icon({ + 'marker-size': 'large', + 'marker-symbol': 'building', + 'marker-color': '#fa0' + }) + }).addTo(map); + var name = 'Name: ' + item[0]; + placeMarker.bindPopup(name, { + closeButton: true + }); + placeMarker.on('click', function (e) { + map.panTo([item[2], item[3]]); + selectPlace(item[1]); + }); + }); + //add an event listener + selectEl.addEventListener('change', function () { + if (selectEl.value !== 'no-location') { + var placeLat = selectEl[selectEl.selectedIndex].dataset.latitude; + var placeLon = selectEl[selectEl.selectedIndex].dataset.longitude; + map.panTo([placeLat, placeLon]); + } + }); + } + //add a button to add a new place + var newLocButton = document.createElement('button'); + newLocButton.setAttribute('type', 'button'); + newLocButton.setAttribute('id', 'create-new-place'); + newLocButton.appendChild(document.createTextNode('Create New Place?')); + //the event listener + newLocButton.addEventListener('click', function() { + //add the form elements + var nameLabel = document.createElement('label'); + nameLabel.setAttribute('for', 'place-name'); + nameLabel.classList.add('place-label') + nameLabel.appendChild(document.createTextNode('Place Name:')); + var nameEl = document.createElement('input'); + nameEl.setAttribute('placeholder', 'Name'); + nameEl.setAttribute('name', 'place-name'); + nameEl.setAttribute('id', 'place-name'); + nameEl.setAttribute('type', 'text'); + var descLabel = document.createElement('label'); + descLabel.setAttribute('for', 'place-description'); + descLabel.classList.add('place-label'); + descLabel.appendChild(document.createTextNode('Place Description:')); + var descEl = document.createElement('input'); + descEl.setAttribute('placeholder', 'Description'); + descEl.setAttribute('name', 'place-description'); + descEl.setAttribute('id', 'place-description'); + descEl.setAttribute('type', 'text'); + var latLabel = document.createElement('label'); + latLabel.setAttribute('for', 'place-latitude'); + latLabel.classList.add('place-label'); + latLabel.appendChild(document.createTextNode('Place Latitude:')); + var latEl = document.createElement('input'); + latEl.setAttribute('name', 'place-latitude'); + latEl.setAttribute('id', 'place-latitude'); + latEl.setAttribute('type', 'text'); + latEl.value = getLatitudeFromMapboxMarker(marker.getLatLng()); + var lonLabel = document.createElement('label'); + lonLabel.setAttribute('for', 'place-longitude'); + lonLabel.classList.add('place-label'); + lonLabel.appendChild(document.createTextNode('Place Longitude:')); + var lonEl = document.createElement('input'); + lonEl.setAttribute('name', 'place-longitude'); + lonEl.setAttribute('id', 'place-longitude'); + lonEl.setAttribute('type', 'text'); + lonEl.value = getLongitudeFromMapboxMarker(marker.getLatLng()); + var placeSubmit = document.createElement('button'); + placeSubmit.setAttribute('id', 'place-submit'); + placeSubmit.setAttribute('value', 'Submit New Place'); + placeSubmit.setAttribute('name', 'place-submit'); + placeSubmit.setAttribute('type', 'button'); + placeSubmit.appendChild(document.createTextNode('Submit New Place')); + form.appendChild(nameLabel); + form.appendChild(nameEl); + form.appendChild(descLabel); + form.appendChild(descEl); + form.appendChild(latLabel); + form.appendChild(latEl); + form.appendChild(lonLabel); + form.appendChild(lonEl); + form.appendChild(placeSubmit); + //the event listener for the new place form + placeSubmit.addEventListener('click', function () { + //create the form data to send + var formData = new FormData(); + formData.append('place-name', document.querySelector('#place-name').value); + formData.append('place-description', document.querySelector('#place-description').value); + formData.append('place-latitude', document.querySelector('#place-latitude').value); + formData.append('place-longitude', document.querySelector('#place-longitude').value); + //post the new place + fetch('/places/new', { + //send cookies with the request + credentials: 'same-origin', + method: 'post', + body: formData + }) + .then(status) + .then(json) + .then(function (placeJson) { + //create the slug from the url + var urlParts = placeJson.split('/'); + var slug = urlParts.pop(); + //remove un-needed form elements + form.removeChild(document.querySelector('#place-name')); + form.removeChild(document.querySelector('#place-description')); + form.removeChild(document.querySelector('#place-latitude')); + form.removeChild(document.querySelector('#place-longitude')); + var labels = document.querySelectorAll('.place-label'); + for (var label of labels) { + form.removeChild(label); + } + form.removeChild(document.querySelector('#place-submit')); + form.removeChild(document.querySelector('#create-new-place')); + //remove location marker + map.removeLayer(marker); + //add place marker + var newOption = document.createElement('option'); + newOption.setAttribute('value', slug); + newOption.appendChild(document.createTextNode(placeJson['name'])); + newOption.dataset.latitude = placeJson['latitude']; + newOption.dataset.longitude = placeJson['longitude']; + selectEl.appendChild(newOption); + var newPlaceMarker = L.marker([placeJson['latitude'], placeJson['longitude']], { + icon: L.mapbox.marker.icon({ + 'marker-size': 'large', + 'marker-symbol': 'building', + 'marker-color': '#fa0' + }) + }).addTo(map); + var newName = 'Name: ' + placeJson['name']; + newPlaceMarker.bindPopup(newName, { + closeButton: true + }); + newPlaceMarker.on('click', function (e) { + map.panTo([placeJson['latitude'], placeJson['longitude']]); + selectPlace(slug); + }); + //make selected + selectPlace(slug); + }).catch(function (placeError) { + console.log(placeError); + }); + }) + }); + form.insertBefore(newLocButton, div); +} + +function parseLocation(point) { + var re = /\((.*)\)/; + var resultArray = re.exec(point); + var location = resultArray[1].split(' '); + + return [location[1], location[0]]; +} + +function selectPlace(slug) { + document.querySelector('select [value=' + slug + ']').selected = true; +} + +function getLatitudeFromMapboxMarker(latlng) { + var resultArray = /\((.*)\)/.exec(latlng); + var location = resultArray[1].split(' '); + + return location[0].replace(',', ''); +} + +function getLongitudeFromMapboxMarker(latlng) { + var resultArray = /\((.*)\)/.exec(latlng); + var location = resultArray[1].split(' '); + + return location[1]; +} + +function status(response) { + if (response.status >= 200 && response.status < 300) { + return Promise.resolve(response); + } else { + return Promise.reject(new Error(response.statusText)); + } +} + +function json(response) { + return response.json(); +} diff --git a/public/assets/js/newplace.js b/public/assets/js/newplace.js new file mode 100644 index 00000000..166031a4 --- /dev/null +++ b/public/assets/js/newplace.js @@ -0,0 +1,45 @@ +var button = document.querySelector('#locate'); + +if (button.addEventListener) { + button.addEventListener('click', getLocation); +} else { + button.attachEvent('onclick', getLocation); +} + +function getLocation() { + if ('geolocation' in navigator) { + navigator.geolocation.getCurrentPosition(function(position) { + updateForm(position.coords.latitude, position.coords.longitude); + addMap(position.coords.latitude, position.coords.longitude); + }); + } else { + console.log('I need to do something when geoloaction isn’t available.'); + } +} + +function updateForm(latitude, longitude) { + var inputLatitude = document.querySelector('#latitude'); + var inputLongitude = document.querySelector('#longitude'); + inputLatitude.value = latitude; + inputLongitude.value = longitude; +} + +function addMap(latitude, longitude) { + var form = document.querySelector('form'); + var div = document.createElement('div'); + div.setAttribute('id', 'map'); + form.appendChild(div); + L.mapbox.accessToken = 'pk.eyJ1Ijoiam9ubnliYXJuZXMiLCJhIjoiVlpndW1EYyJ9.aP9fxAqLKh7lj0LpFh5k1w'; + var map = L.mapbox.map('map', 'jonnybarnes.gnoihnim') + .setView([latitude, longitude], 15) + .addLayer(L.mapbox.tileLayer('jonnybarnes.gnoihnim', { + detectRetina: true, + })); + var marker = L.marker([latitude, longitude], { + draggable: true, + }).addTo(map); + marker.on('dragend', function () { + var markerLocation = marker.getLatLng(); + updateForm(markerLocation.lat, markerLocation.lng); + }); +} diff --git a/public/assets/js/prism.js b/public/assets/js/prism.js new file mode 100644 index 00000000..b00d099e --- /dev/null +++ b/public/assets/js/prism.js @@ -0,0 +1,15 @@ +/* http://prismjs.com/download.html?themes=prism-dark&languages=markup+css+clike+javascript+git+http+markdown+php+php-extras+scss+sql&plugins=line-numbers+show-invisibles */ +var _self="undefined"!=typeof window?window:"undefined"!=typeof WorkerGlobalScope&&self instanceof WorkerGlobalScope?self:{},Prism=function(){var e=/\blang(?:uage)?-(?!\*)(\w+)\b/i,t=_self.Prism={util:{encode:function(e){return e instanceof n?new n(e.type,t.util.encode(e.content),e.alias):"Array"===t.util.type(e)?e.map(t.util.encode):e.replace(/&/g,"&").replace(/e.length)break e;if(!(d instanceof a)){u.lastIndex=0;var m=u.exec(d);if(m){c&&(f=m[1].length);var y=m.index-1+f,m=m[0].slice(f),v=m.length,k=y+v,b=d.slice(0,y+1),w=d.slice(k+1),N=[p,1];b&&N.push(b);var O=new a(l,g?t.tokenize(m,g):m,h);N.push(O),w&&N.push(w),Array.prototype.splice.apply(r,N)}}}}}return r},hooks:{all:{},add:function(e,n){var a=t.hooks.all;a[e]=a[e]||[],a[e].push(n)},run:function(e,n){var a=t.hooks.all[e];if(a&&a.length)for(var r,i=0;r=a[i++];)r(n)}}},n=t.Token=function(e,t,n){this.type=e,this.content=t,this.alias=n};if(n.stringify=function(e,a,r){if("string"==typeof e)return e;if("Array"===t.util.type(e))return e.map(function(t){return n.stringify(t,a,e)}).join("");var i={type:e.type,content:n.stringify(e.content,a,r),tag:"span",classes:["token",e.type],attributes:{},language:a,parent:r};if("comment"==i.type&&(i.attributes.spellcheck="true"),e.alias){var l="Array"===t.util.type(e.alias)?e.alias:[e.alias];Array.prototype.push.apply(i.classes,l)}t.hooks.run("wrap",i);var o="";for(var s in i.attributes)o+=s+'="'+(i.attributes[s]||"")+'"';return"<"+i.tag+' class="'+i.classes.join(" ")+'" '+o+">"+i.content+""},!_self.document)return _self.addEventListener?(_self.addEventListener("message",function(e){var n=JSON.parse(e.data),a=n.language,r=n.code;_self.postMessage(JSON.stringify(t.util.encode(t.tokenize(r,t.languages[a])))),_self.close()},!1),_self.Prism):_self.Prism;var a=document.getElementsByTagName("script");return a=a[a.length-1],a&&(t.filename=a.src,document.addEventListener&&!a.hasAttribute("data-manual")&&document.addEventListener("DOMContentLoaded",t.highlightAll)),_self.Prism}();"undefined"!=typeof module&&module.exports&&(module.exports=Prism);; +Prism.languages.markup={comment://,prolog:/<\?[\w\W]+?\?>/,doctype://,cdata://i,tag:{pattern:/<\/?[^\s>\/]+(?:\s+[^\s>\/=]+(?:=(?:("|')(?:\\\1|\\?(?!\1)[\w\W])*\1|[^\s'">=]+))?)*\s*\/?>/i,inside:{tag:{pattern:/^<\/?[^\s>\/]+/i,inside:{punctuation:/^<\/?/,namespace:/^[^\s>\/:]+:/}},"attr-value":{pattern:/=(?:('|")[\w\W]*?(\1)|[^\s>]+)/i,inside:{punctuation:/[=>"']/}},punctuation:/\/?>/,"attr-name":{pattern:/[^\s>\/]+/,inside:{namespace:/^[^\s>\/:]+:/}}}},entity:/&#?[\da-z]{1,8};/i},Prism.hooks.add("wrap",function(t){"entity"===t.type&&(t.attributes.title=t.content.replace(/&/,"&"))});; +Prism.languages.css={comment:/\/\*[\w\W]*?\*\//,atrule:{pattern:/@[\w-]+?.*?(;|(?=\s*\{))/i,inside:{rule:/@[\w-]+/}},url:/url\((?:(["'])(\\(?:\r\n|[\w\W])|(?!\1)[^\\\r\n])*\1|.*?)\)/i,selector:/[^\{\}\s][^\{\};]*?(?=\s*\{)/,string:/("|')(\\(?:\r\n|[\w\W])|(?!\1)[^\\\r\n])*\1/,property:/(\b|\B)[\w-]+(?=\s*:)/i,important:/\B!important\b/i,"function":/[-a-z0-9]+(?=\()/i,punctuation:/[(){};:]/},Prism.languages.css.atrule.inside.rest=Prism.util.clone(Prism.languages.css),Prism.languages.markup&&(Prism.languages.insertBefore("markup","tag",{style:{pattern:/[\w\W]*?<\/style>/i,inside:{tag:{pattern:/|<\/style>/i,inside:Prism.languages.markup.tag.inside},rest:Prism.languages.css},alias:"language-css"}}),Prism.languages.insertBefore("inside","attr-value",{"style-attr":{pattern:/\s*style=("|').*?\1/i,inside:{"attr-name":{pattern:/^\s*style/i,inside:Prism.languages.markup.tag.inside},punctuation:/^\s*=\s*['"]|['"]\s*$/,"attr-value":{pattern:/.+/i,inside:Prism.languages.css}},alias:"language-css"}},Prism.languages.markup.tag));; +Prism.languages.clike={comment:[{pattern:/(^|[^\\])\/\*[\w\W]*?\*\//,lookbehind:!0},{pattern:/(^|[^\\:])\/\/.*/,lookbehind:!0}],string:/("|')(\\(?:\r\n|[\s\S])|(?!\1)[^\\\r\n])*\1/,"class-name":{pattern:/((?:\b(?:class|interface|extends|implements|trait|instanceof|new)\s+)|(?:catch\s+\())[a-z0-9_\.\\]+/i,lookbehind:!0,inside:{punctuation:/(\.|\\)/}},keyword:/\b(if|else|while|do|for|return|in|instanceof|function|new|try|throw|catch|finally|null|break|continue)\b/,"boolean":/\b(true|false)\b/,"function":/[a-z0-9_]+(?=\()/i,number:/\b-?(0x[\dA-Fa-f]+|\d*\.?\d+([Ee]-?\d+)?)\b/,operator:/[-+]{1,2}|!|<=?|>=?|={1,3}|&{1,2}|\|?\||\?|\*|\/|~|\^|%/,punctuation:/[{}[\];(),.:]/};; +Prism.languages.javascript=Prism.languages.extend("clike",{keyword:/\b(as|async|await|break|case|catch|class|const|continue|debugger|default|delete|do|else|enum|export|extends|false|finally|for|from|function|get|if|implements|import|in|instanceof|interface|let|new|null|of|package|private|protected|public|return|set|static|super|switch|this|throw|true|try|typeof|var|void|while|with|yield)\b/,number:/\b-?(0x[\dA-Fa-f]+|0b[01]+|0o[0-7]+|\d*\.?\d+([Ee][+-]?\d+)?|NaN|Infinity)\b/,"function":/(?!\d)[a-z0-9_$]+(?=\()/i}),Prism.languages.insertBefore("javascript","keyword",{regex:{pattern:/(^|[^/])\/(?!\/)(\[.+?]|\\.|[^/\\\r\n])+\/[gimyu]{0,5}(?=\s*($|[\r\n,.;})]))/,lookbehind:!0}}),Prism.languages.insertBefore("javascript","class-name",{"template-string":{pattern:/`(?:\\`|\\?[^`])*`/,inside:{interpolation:{pattern:/\$\{[^}]+\}/,inside:{"interpolation-punctuation":{pattern:/^\$\{|\}$/,alias:"punctuation"},rest:Prism.languages.javascript}},string:/[\s\S]+/}}}),Prism.languages.markup&&Prism.languages.insertBefore("markup","tag",{script:{pattern:/[\w\W]*?<\/script>/i,inside:{tag:{pattern:/|<\/script>/i,inside:Prism.languages.markup.tag.inside},rest:Prism.languages.javascript},alias:"language-javascript"}});; +Prism.languages.git={comment:/^#.*$/m,string:/("|')(\\?.)*?\1/m,command:{pattern:/^.*\$ git .*$/m,inside:{parameter:/\s(--|-)\w+/m}},coord:/^@@.*@@$/m,deleted:/^-(?!-).+$/m,inserted:/^\+(?!\+).+$/m,commit_sha1:/^commit \w{40}$/m};; +Prism.languages.http={"request-line":{pattern:/^(POST|GET|PUT|DELETE|OPTIONS|PATCH|TRACE|CONNECT)\b\shttps?:\/\/\S+\sHTTP\/[0-9.]+/,inside:{property:/^\b(POST|GET|PUT|DELETE|OPTIONS|PATCH|TRACE|CONNECT)\b/,"attr-name":/:\w+/}},"response-status":{pattern:/^HTTP\/1.[01] [0-9]+.*/,inside:{property:/[0-9]+[A-Z\s-]+$/i}},keyword:/^[\w-]+:(?=.+)/m};var httpLanguages={"application/json":Prism.languages.javascript,"application/xml":Prism.languages.markup,"text/xml":Prism.languages.markup,"text/html":Prism.languages.markup};for(var contentType in httpLanguages)if(httpLanguages[contentType]){var options={};options[contentType]={pattern:new RegExp("(content-type:\\s*"+contentType+"[\\w\\W]*?)\\n\\n[\\w\\W]*","i"),lookbehind:!0,inside:{rest:httpLanguages[contentType]}},Prism.languages.insertBefore("http","keyword",options)}; +Prism.languages.markdown=Prism.languages.extend("markup",{}),Prism.languages.insertBefore("markdown","prolog",{blockquote:{pattern:/(^|\n)>(?:[\t ]*>)*/,lookbehind:!0,alias:"punctuation"},code:[{pattern:/(^|\n)(?: {4}|\t).+/,lookbehind:!0,alias:"keyword"},{pattern:/``.+?``|`[^`\n]+`/,alias:"keyword"}],title:[{pattern:/\w+.*\n(?:==+|--+)/,alias:"important",inside:{punctuation:/==+$|--+$/}},{pattern:/((?:^|\n)\s*)#+.+/,lookbehind:!0,alias:"important",inside:{punctuation:/^#+|#+$/}}],hr:{pattern:/((?:^|\n)\s*)([*-])([\t ]*\2){2,}(?=\s*(?:\n|$))/,lookbehind:!0,alias:"punctuation"},list:{pattern:/((?:^|\n)\s*)(?:[*+-]|\d+\.)(?=[\t ].)/,lookbehind:!0,alias:"punctuation"},"url-reference":{pattern:/!?\[[^\]]+\]:[\t ]+(?:\S+|<(?:[^>]|\\>)+>)(?:[\t ]+(?:"(?:[^"]|\\")*"|'(?:[^']|\\')*'|\((?:[^)]|\\\))*\)))?/,inside:{variable:{pattern:/^(!?\[)[^\]]+/,lookbehind:!0},string:/(?:"(?:[^"]|\\")*"|'(?:[^']|\\')*'|\((?:[^)]|\\\))*\))$/,punctuation:/[[\]\(\)<>:]/},alias:"url"},bold:{pattern:/(^|[^\\])(\*\*|__)(?:\n(?!\n)|.)+?\2/,lookbehind:!0,inside:{punctuation:/^\*\*|^__|\*\*\s*$|__\s*$/}},italic:{pattern:/(^|[^\\])(?:\*(?:\n(?!\n)|.)+?\*|_(?:\n(?!\n)|.)+?_)/,lookbehind:!0,inside:{punctuation:/^[*_]|[*_]$/}},url:{pattern:/!?\[[^\]]+\](?:\([^\s)]+(?:[\t ]+"(?:[^"]|\\")*")?\)| ?\[[^\]\n]*\])/,inside:{variable:{pattern:/(!?\[)[^\]]+(?=\]$)/,lookbehind:!0},string:{pattern:/"(?:[^"]|\\")*"(?=\)$)/}}}}),Prism.languages.markdown.bold.inside.url=Prism.util.clone(Prism.languages.markdown.url),Prism.languages.markdown.italic.inside.url=Prism.util.clone(Prism.languages.markdown.url),Prism.languages.markdown.bold.inside.italic=Prism.util.clone(Prism.languages.markdown.italic),Prism.languages.markdown.italic.inside.bold=Prism.util.clone(Prism.languages.markdown.bold);; +Prism.languages.php=Prism.languages.extend("clike",{keyword:/\b(and|or|xor|array|as|break|case|cfunction|class|const|continue|declare|default|die|do|else|elseif|enddeclare|endfor|endforeach|endif|endswitch|endwhile|extends|for|foreach|function|include|include_once|global|if|new|return|static|switch|use|require|require_once|var|while|abstract|interface|public|implements|private|protected|parent|throw|null|echo|print|trait|namespace|final|yield|goto|instanceof|finally|try|catch)\b/i,constant:/\b[A-Z0-9_]{2,}\b/,comment:{pattern:/(^|[^\\])(\/\*[\w\W]*?\*\/|(^|[^:])(\/\/).*?(\r?\n|$))/,lookbehind:!0}}),Prism.languages.insertBefore("php","class-name",{"shell-comment":{pattern:/(^|[^\\])#.*?(\r?\n|$)/,lookbehind:!0,alias:"comment"}}),Prism.languages.insertBefore("php","keyword",{delimiter:/(\?>|<\?php|<\?)/i,variable:/(\$\w+)\b/i,"package":{pattern:/(\\|namespace\s+|use\s+)[\w\\]+/,lookbehind:!0,inside:{punctuation:/\\/}}}),Prism.languages.insertBefore("php","operator",{property:{pattern:/(->)[\w]+/,lookbehind:!0}}),Prism.languages.markup&&(Prism.hooks.add("before-highlight",function(e){"php"===e.language&&(e.tokenStack=[],e.backupCode=e.code,e.code=e.code.replace(/(?:<\?php|<\?)[\w\W]*?(?:\?>)/gi,function(n){return e.tokenStack.push(n),"{{{PHP"+e.tokenStack.length+"}}}"}))}),Prism.hooks.add("before-insert",function(e){"php"===e.language&&(e.code=e.backupCode,delete e.backupCode)}),Prism.hooks.add("after-highlight",function(e){if("php"===e.language){for(var n,a=0;n=e.tokenStack[a];a++)e.highlightedCode=e.highlightedCode.replace("{{{PHP"+(a+1)+"}}}",Prism.highlight(n,e.grammar,"php"));e.element.innerHTML=e.highlightedCode}}),Prism.hooks.add("wrap",function(e){"php"===e.language&&"markup"===e.type&&(e.content=e.content.replace(/(\{\{\{PHP[0-9]+\}\}\})/g,'$1'))}),Prism.languages.insertBefore("php","comment",{markup:{pattern:/<[^?]\/?(.*?)>/,inside:Prism.languages.markup},php:/\{\{\{PHP[0-9]+\}\}\}/}));; +Prism.languages.insertBefore("php","variable",{"this":/\$this/,global:/\$_?(GLOBALS|SERVER|GET|POST|FILES|REQUEST|SESSION|ENV|COOKIE|HTTP_RAW_POST_DATA|argc|argv|php_errormsg|http_response_header)/,scope:{pattern:/\b[\w\\]+::/,inside:{keyword:/(static|self|parent)/,punctuation:/(::|\\)/}}});; +Prism.languages.scss=Prism.languages.extend("css",{comment:{pattern:/(^|[^\\])(\/\*[\w\W]*?\*\/|\/\/.*?(\r?\n|$))/,lookbehind:!0},atrule:{pattern:/@[\w-]+(?:\([^()]+\)|[^(])*?(?=\s+(\{|;))/i,inside:{rule:/@[\w-]+/}},url:/([-a-z]+-)*url(?=\()/i,selector:{pattern:/([^@;\{\}\(\)]?([^@;\{\}\(\)]|&|#\{\$[-_\w]+\})+)(?=\s*\{(\}|\s|[^\}]+(:|\{)[^\}]+))/m,inside:{placeholder:/%[-_\w]+/i}}}),Prism.languages.insertBefore("scss","atrule",{keyword:/@(if|else if|else|for|each|while|import|extend|debug|warn|mixin|include|function|return|content)|(?=@for\s+\$[-_\w]+\s)+from/i}),Prism.languages.insertBefore("scss","property",{variable:/((\$[-_\w]+)|(#\{\$[-_\w]+\}))/i}),Prism.languages.insertBefore("scss","function",{placeholder:{pattern:/%[-_\w]+/i,alias:"selector"},statement:/\B!(default|optional)\b/i,"boolean":/\b(true|false)\b/,"null":/\b(null)\b/,operator:/\s+([-+]{1,2}|={1,2}|!=|\|?\||\?|\*|\/|%)\s+/}),Prism.languages.scss.atrule.inside.rest=Prism.util.clone(Prism.languages.scss);; +Prism.languages.sql={comment:{pattern:/(^|[^\\])(\/\*[\w\W]*?\*\/|((--)|(\/\/)|#).*?(\r?\n|$))/,lookbehind:!0},string:{pattern:/(^|[^@])("|')(\\?[\s\S])*?\2/,lookbehind:!0},variable:/@[\w.$]+|@("|'|`)(\\?[\s\S])+?\1/,"function":/\b(?:COUNT|SUM|AVG|MIN|MAX|FIRST|LAST|UCASE|LCASE|MID|LEN|ROUND|NOW|FORMAT)(?=\s*\()/i,keyword:/\b(?:ACTION|ADD|AFTER|ALGORITHM|ALTER|ANALYZE|APPLY|AS|ASC|AUTHORIZATION|BACKUP|BDB|BEGIN|BERKELEYDB|BIGINT|BINARY|BIT|BLOB|BOOL|BOOLEAN|BREAK|BROWSE|BTREE|BULK|BY|CALL|CASCADE|CASCADED|CASE|CHAIN|CHAR VARYING|CHARACTER VARYING|CHECK|CHECKPOINT|CLOSE|CLUSTERED|COALESCE|COLUMN|COLUMNS|COMMENT|COMMIT|COMMITTED|COMPUTE|CONNECT|CONSISTENT|CONSTRAINT|CONTAINS|CONTAINSTABLE|CONTINUE|CONVERT|CREATE|CROSS|CURRENT|CURRENT_DATE|CURRENT_TIME|CURRENT_TIMESTAMP|CURRENT_USER|CURSOR|DATA|DATABASE|DATABASES|DATETIME|DBCC|DEALLOCATE|DEC|DECIMAL|DECLARE|DEFAULT|DEFINER|DELAYED|DELETE|DENY|DESC|DESCRIBE|DETERMINISTIC|DISABLE|DISCARD|DISK|DISTINCT|DISTINCTROW|DISTRIBUTED|DO|DOUBLE|DOUBLE PRECISION|DROP|DUMMY|DUMP|DUMPFILE|DUPLICATE KEY|ELSE|ENABLE|ENCLOSED BY|END|ENGINE|ENUM|ERRLVL|ERRORS|ESCAPE|ESCAPED BY|EXCEPT|EXEC|EXECUTE|EXIT|EXPLAIN|EXTENDED|FETCH|FIELDS|FILE|FILLFACTOR|FIRST|FIXED|FLOAT|FOLLOWING|FOR|FOR EACH ROW|FORCE|FOREIGN|FREETEXT|FREETEXTTABLE|FROM|FULL|FUNCTION|GEOMETRY|GEOMETRYCOLLECTION|GLOBAL|GOTO|GRANT|GROUP|HANDLER|HASH|HAVING|HOLDLOCK|IDENTITY|IDENTITY_INSERT|IDENTITYCOL|IF|IGNORE|IMPORT|INDEX|INFILE|INNER|INNODB|INOUT|INSERT|INT|INTEGER|INTERSECT|INTO|INVOKER|ISOLATION LEVEL|JOIN|KEY|KEYS|KILL|LANGUAGE SQL|LAST|LEFT|LIMIT|LINENO|LINES|LINESTRING|LOAD|LOCAL|LOCK|LONGBLOB|LONGTEXT|MATCH|MATCHED|MEDIUMBLOB|MEDIUMINT|MEDIUMTEXT|MERGE|MIDDLEINT|MODIFIES SQL DATA|MODIFY|MULTILINESTRING|MULTIPOINT|MULTIPOLYGON|NATIONAL|NATIONAL CHAR VARYING|NATIONAL CHARACTER|NATIONAL CHARACTER VARYING|NATIONAL VARCHAR|NATURAL|NCHAR|NCHAR VARCHAR|NEXT|NO|NO SQL|NOCHECK|NOCYCLE|NONCLUSTERED|NULLIF|NUMERIC|OF|OFF|OFFSETS|ON|OPEN|OPENDATASOURCE|OPENQUERY|OPENROWSET|OPTIMIZE|OPTION|OPTIONALLY|ORDER|OUT|OUTER|OUTFILE|OVER|PARTIAL|PARTITION|PERCENT|PIVOT|PLAN|POINT|POLYGON|PRECEDING|PRECISION|PREV|PRIMARY|PRINT|PRIVILEGES|PROC|PROCEDURE|PUBLIC|PURGE|QUICK|RAISERROR|READ|READS SQL DATA|READTEXT|REAL|RECONFIGURE|REFERENCES|RELEASE|RENAME|REPEATABLE|REPLICATION|REQUIRE|RESTORE|RESTRICT|RETURN|RETURNS|REVOKE|RIGHT|ROLLBACK|ROUTINE|ROWCOUNT|ROWGUIDCOL|ROWS?|RTREE|RULE|SAVE|SAVEPOINT|SCHEMA|SELECT|SERIAL|SERIALIZABLE|SESSION|SESSION_USER|SET|SETUSER|SHARE MODE|SHOW|SHUTDOWN|SIMPLE|SMALLINT|SNAPSHOT|SOME|SONAME|START|STARTING BY|STATISTICS|STATUS|STRIPED|SYSTEM_USER|TABLE|TABLES|TABLESPACE|TEMP(?:ORARY)?|TEMPTABLE|TERMINATED BY|TEXT|TEXTSIZE|THEN|TIMESTAMP|TINYBLOB|TINYINT|TINYTEXT|TO|TOP|TRAN|TRANSACTION|TRANSACTIONS|TRIGGER|TRUNCATE|TSEQUAL|TYPE|TYPES|UNBOUNDED|UNCOMMITTED|UNDEFINED|UNION|UNPIVOT|UPDATE|UPDATETEXT|USAGE|USE|USER|USING|VALUE|VALUES|VARBINARY|VARCHAR|VARCHARACTER|VARYING|VIEW|WAITFOR|WARNINGS|WHEN|WHERE|WHILE|WITH|WITH ROLLUP|WITHIN|WORK|WRITE|WRITETEXT)\b/i,"boolean":/\b(?:TRUE|FALSE|NULL)\b/i,number:/\b-?(0x)?\d*\.?[\da-f]+\b/,operator:/\b(?:ALL|AND|ANY|BETWEEN|EXISTS|IN|LIKE|NOT|OR|IS|UNIQUE|CHARACTER SET|COLLATE|DIV|OFFSET|REGEXP|RLIKE|SOUNDS LIKE|XOR)\b|[-+]|!|[=<>]{1,2}|(&){1,2}|\|?\||\?|\*|\//i,punctuation:/[;[\]()`,.]/};; +Prism.hooks.add("complete",function(e){if(e.code){var t=e.element.parentNode,s=/\s*\bline-numbers\b\s*/;if(t&&/pre/i.test(t.nodeName)&&(s.test(t.className)||s.test(e.element.className))&&!e.element.querySelector(".line-numbers-rows")){s.test(e.element.className)&&(e.element.className=e.element.className.replace(s,"")),s.test(t.className)||(t.className+=" line-numbers");var a,n=e.code.match(/\n(?!$)/g).length+1,l=new Array(n+1);l=l.join(""),a=document.createElement("span"),a.className="line-numbers-rows",a.innerHTML=l,t.hasAttribute("data-start")&&(t.style.counterReset="linenumber "+(parseInt(t.getAttribute("data-start"),10)-1)),e.element.appendChild(a)}}});; +!function(){if(window.Prism)for(var r in Prism.languages){var g=Prism.languages[r];g.tab=/\t/g,g.crlf=/\r\n/g,g.lf=/\n/g,g.cr=/\r/g}}();; diff --git a/public/assets/js/store2.min.js b/public/assets/js/store2.min.js new file mode 100644 index 00000000..72aff0ab --- /dev/null +++ b/public/assets/js/store2.min.js @@ -0,0 +1,5 @@ +/*! store2 - v2.3.2 - 2015-10-27 +* Copyright (c) 2015 Nathan Bubna; Licensed MIT, GPL */ + +!function(a,b){var c={version:"2.3.2",areas:{},apis:{},inherit:function(a,b){for(var c in a)b.hasOwnProperty(c)||(b[c]=a[c]);return b},stringify:function(a){return void 0===a||"function"==typeof a?a+"":JSON.stringify(a)},parse:function(a){try{return JSON.parse(a)}catch(b){return a}},fn:function(a,b){c.storeAPI[a]=b;for(var d in c.apis)c.apis[d][a]=b},get:function(a,b){return a.getItem(b)},set:function(a,b,c){a.setItem(b,c)},remove:function(a,b){a.removeItem(b)},key:function(a,b){return a.key(b)},length:function(a){return a.length},clear:function(a){a.clear()},Store:function(a,b,d){var e=c.inherit(c.storeAPI,function(a,b,c){return 0===arguments.length?e.getAll():void 0!==b?e.set(a,b,c):"string"==typeof a||"number"==typeof a?e.get(a):a?e.setAll(a,b):e.clear()});e._id=a;try{var f="_safariPrivate_";b.setItem(f,"sucks"),e._area=b,b.removeItem(f)}catch(g){}return e._area||(e._area=c.inherit(c.storageAPI,{items:{},name:"fake"})),e._ns=d||"",c.areas[a]||(c.areas[a]=e._area),c.apis[e._ns+e._id]||(c.apis[e._ns+e._id]=e),e},storeAPI:{area:function(a,b){var d=this[a];return d&&d.area||(d=c.Store(a,b,this._ns),this[a]||(this[a]=d)),d},namespace:function(a,b){if(!a)return this._ns?this._ns.substring(0,this._ns.length-1):"";var d=a,e=this[d];return e&&e.namespace||(e=c.Store(this._id,this._area,this._ns+d+"."),this[d]||(this[d]=e),b||e.area("session",c.areas.session)),e},isFake:function(){return"fake"===this._area.name},toString:function(){return"store"+(this._ns?"."+this.namespace():"")+"["+this._id+"]"},has:function(a){return this._area.has?this._area.has(this._in(a)):!!(this._in(a)in this._area)},size:function(){return this.keys().length},each:function(a,b){for(var d=0,e=c.length(this._area);e>d;d++){var f=this._out(c.key(this._area,d));if(void 0!==f&&a.call(this,f,b||this.get(f))===!1)break;e>c.length(this._area)&&(e--,d--)}return b||this},keys:function(){return this.each(function(a,b){b.push(a)},[])},get:function(a,b){var d=c.get(this._area,this._in(a));return null!==d?c.parse(d):b||d},getAll:function(){return this.each(function(a,b){b[a]=this.get(a)},{})},set:function(a,b,d){var e=this.get(a);return null!=e&&d===!1?b:c.set(this._area,this._in(a),c.stringify(b),d)||e},setAll:function(a,b){var c,d;for(var e in a)d=a[e],this.set(e,d,b)!==d&&(c=!0);return c},remove:function(a){var b=this.get(a);return c.remove(this._area,this._in(a)),b},clear:function(){return this._ns?this.each(function(a){c.remove(this._area,this._in(a))},1):c.clear(this._area),this},clearAll:function(){var a=this._area;for(var b in c.areas)c.areas.hasOwnProperty(b)&&(this._area=c.areas[b],this.clear());return this._area=a,this},_in:function(a){return"string"!=typeof a&&(a=c.stringify(a)),this._ns?this._ns+a:a},_out:function(a){return this._ns?a&&0===a.indexOf(this._ns)?a.substring(this._ns.length):void 0:a}},storageAPI:{length:0,has:function(a){return this.items.hasOwnProperty(a)},key:function(a){var b=0;for(var c in this.items)if(this.has(c)&&a===b++)return c},setItem:function(a,b){this.has(a)||this.length++,this.items[a]=b},removeItem:function(a){this.has(a)&&(delete this.items[a],this.length--)},getItem:function(a){return this.has(a)?this.items[a]:null},clear:function(){for(var a in this.list)this.removeItem(a)},toString:function(){return this.length+" items in "+this.name+"Storage"}}};a.store&&(c.conflict=a.store);var d=c.Store("local",function(){try{return localStorage}catch(a){}}());d.local=d,d._=c,d.area("session",function(){try{return sessionStorage}catch(a){}}()),a.store=d,"function"==typeof b&&void 0!==b.amd?b(function(){return d}):"undefined"!=typeof module&&module.exports&&(module.exports=d)}(this,this.define); +//# sourceMappingURL=store2.min.js.map \ No newline at end of file diff --git a/public/assets/old-shorturls.json b/public/assets/old-shorturls.json new file mode 100644 index 00000000..26b0e4c4 --- /dev/null +++ b/public/assets/old-shorturls.json @@ -0,0 +1,19 @@ +{ + "RbrS": "https://jonnybarnes.net/note/1", + "pfua": "https://jonnybarnes.net/note/2", + "HQ8X": "https://jonnybarnes.net/note/3", + "7Duc": "https://jonnybarnes.net/note/4", + "m0vZ": "https://jonnybarnes.net/note/5", + "uB95": "https://jonnybarnes.net/note/6", + "yUx8": "https://jonnybarnes.net/note/7", + "tMLB": "https://jonnybarnes.net/note/8", + "a1HU": "https://jonnybarnes.net/note/9", + "rx3e": "https://jonnybarnes.net/note/10", + "dW3p": "https://jonnybarnes.net/note/11", + "_6za": "https://jonnybarnes.net/note/12", + "eTvB": "https://jonnybarnes.net/note/13", + "6kMh": "https://jonnybarnes.net/note/14", + "T72f": "https://jonnybarnes.net/note/15", + "enot": "https://jonnybarnes.net/note/16", + "QCDv": "https://jonnybarnes.net/note/17" +} \ No newline at end of file diff --git a/public/assets/profile-images/.gitignore b/public/assets/profile-images/.gitignore new file mode 100644 index 00000000..dd9ea438 --- /dev/null +++ b/public/assets/profile-images/.gitignore @@ -0,0 +1,3 @@ +* +!.gitignore +!default-image diff --git a/public/assets/profile-images/default-image b/public/assets/profile-images/default-image new file mode 100644 index 00000000..2a0d3ed7 Binary files /dev/null and b/public/assets/profile-images/default-image differ diff --git a/public/build/assets/css/alertify-d84546f82d.css b/public/build/assets/css/alertify-d84546f82d.css new file mode 100644 index 00000000..ced38bdd --- /dev/null +++ b/public/build/assets/css/alertify-d84546f82d.css @@ -0,0 +1 @@ +.alertify-logs>*{padding:12px 24px;color:#fff;box-shadow:0 2px 5px 0 rgba(0,0,0,.2);border-radius:1px}.alertify-logs>*,.alertify-logs>.default{background:rgba(0,0,0,.8)}.alertify-logs>.error{background:rgba(244,67,54,.8)}.alertify-logs>.success{background:rgba(76,175,80,.9)}.alertify{position:fixed;background-color:rgba(0,0,0,.3);left:0;right:0;top:0;bottom:0;width:100%;height:100%;z-index:2}.alertify.hide{opacity:0;pointer-events:none}.alertify,.alertify.show{box-sizing:border-box;transition:all .33s cubic-bezier(.25,.8,.25,1)}.alertify,.alertify *{box-sizing:border-box}.alertify .dialog{padding:12px}.alertify .alert,.alertify .dialog{width:100%;margin:0 auto;position:relative;top:50%;-webkit-transform:translateY(-50%);transform:translateY(-50%)}.alertify .alert>*,.alertify .dialog>*{width:400px;max-width:95%;margin:0 auto;text-align:center;padding:12px;background:#fff;box-shadow:0 2px 4px -1px rgba(0,0,0,.14),0 4px 5px 0 rgba(0,0,0,.098),0 1px 10px 0 rgba(0,0,0,.084)}.alertify .alert .msg,.alertify .dialog .msg{padding:12px;margin-bottom:12px;margin:0;text-align:left}.alertify .alert input:not(.form-control),.alertify .dialog input:not(.form-control){margin-bottom:15px;width:100%;font-size:100%;padding:12px}.alertify .alert input:not(.form-control):focus,.alertify .dialog input:not(.form-control):focus{outline-offset:-2px}.alertify .alert nav,.alertify .dialog nav{text-align:right}.alertify .alert nav button:not(.btn):not(.pure-button):not(.md-button):not(.mdl-button),.alertify .dialog nav button:not(.btn):not(.pure-button):not(.md-button):not(.mdl-button){background:transparent;box-sizing:border-box;color:rgba(0,0,0,.87);position:relative;outline:0;border:0;display:inline-block;-webkit-align-items:center;-ms-flex-align:center;-ms-grid-row-align:center;align-items:center;padding:0 6px;margin:6px 8px;line-height:36px;min-height:36px;white-space:nowrap;min-width:88px;text-align:center;text-transform:uppercase;font-size:14px;text-decoration:none;cursor:pointer;border:1px solid transparent;border-radius:2px}.alertify .alert nav button:not(.btn):not(.pure-button):not(.md-button):not(.mdl-button):active,.alertify .alert nav button:not(.btn):not(.pure-button):not(.md-button):not(.mdl-button):hover,.alertify .dialog nav button:not(.btn):not(.pure-button):not(.md-button):not(.mdl-button):active,.alertify .dialog nav button:not(.btn):not(.pure-button):not(.md-button):not(.mdl-button):hover{background-color:rgba(0,0,0,.05)}.alertify .alert nav button:not(.btn):not(.pure-button):not(.md-button):not(.mdl-button):focus,.alertify .dialog nav button:not(.btn):not(.pure-button):not(.md-button):not(.mdl-button):focus{border:1px solid rgba(0,0,0,.1)}.alertify .alert nav button.btn,.alertify .dialog nav button.btn{margin:6px 4px}.alertify-logs{position:fixed;z-index:1}.alertify-logs.bottom,.alertify-logs:not(.top){bottom:16px}.alertify-logs.left,.alertify-logs:not(.right){left:16px}.alertify-logs.left>*,.alertify-logs:not(.right)>*{float:left;-webkit-transform:translateZ(0);transform:translateZ(0);height:auto}.alertify-logs.left>.show,.alertify-logs:not(.right)>.show{left:0}.alertify-logs.left>*,.alertify-logs.left>.hide,.alertify-logs:not(.right)>*,.alertify-logs:not(.right)>.hide{left:-110%}.alertify-logs.right{right:16px}.alertify-logs.right>*{float:right;-webkit-transform:translateZ(0);transform:translateZ(0)}.alertify-logs.right>.show{right:0;opacity:1}.alertify-logs.right>*,.alertify-logs.right>.hide{right:-110%;opacity:0}.alertify-logs.top{top:0}.alertify-logs>*{box-sizing:border-box;transition:all .4s cubic-bezier(.25,.8,.25,1);position:relative;clear:both;-webkit-backface-visibility:hidden;backface-visibility:hidden;-webkit-perspective:1000;perspective:1000;max-height:0;margin:0;padding:0;overflow:hidden;opacity:0;pointer-events:none}.alertify-logs>.show{margin-top:12px;opacity:1;max-height:1000px;padding:12px;pointer-events:auto} \ No newline at end of file diff --git a/public/build/assets/css/alertify-d84546f82d.css.br b/public/build/assets/css/alertify-d84546f82d.css.br new file mode 100644 index 00000000..e5b38b0b Binary files /dev/null and b/public/build/assets/css/alertify-d84546f82d.css.br differ diff --git a/public/build/assets/css/alertify-d84546f82d.css.gz b/public/build/assets/css/alertify-d84546f82d.css.gz new file mode 100644 index 00000000..59ce31ea Binary files /dev/null and b/public/build/assets/css/alertify-d84546f82d.css.gz differ diff --git a/public/build/assets/css/global-ef9dfef096.css b/public/build/assets/css/global-ef9dfef096.css new file mode 100644 index 00000000..1750d873 --- /dev/null +++ b/public/build/assets/css/global-ef9dfef096.css @@ -0,0 +1,243 @@ +html { + background: url("/assets/img/escheresque.png"); } + +.map { + height: 150px; } + +html { + box-sizing: border-box; } + +*, *:before, *:after { + box-sizing: inherit; } + +#topheader { + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + -webkit-flex-flow: row; + -ms-flex-flow: row; + flex-flow: row; } + +#topheader a { + padding: 0.5em 1em; } + +nav { + padding-top: 0.5em; } + +.social-list { + padding-left: 2em; } + +.note { + background-color: #eee8d5; + box-shadow: 0 0 10px 2px #93a1a1; + padding: 0.5em 0.5em; + margin-top: 1em; } + +.note:after { + content: " "; + display: block; + height: 0; + clear: both; } + +.note a { + word-wrap: break-word; } + +.note .e-content p:first-child { + margin-top: 0; } + +.note-metadata { + width: 100%; } + +.social-links { + float: right; } + +.social-links a { + text-decoration: none; } + +.icon { + width: auto; + height: 1em; + fill: #268bd2; } + +.reply { + margin-left: 2em; + margin-right: 2em; + font-size: 0.8em; + padding: 0.5em 0.5em; } + +.reply-to { + margin-left: 2em; + margin-right: 2em; + font-size: 0.8em; + padding-top: 2em; } + +.reply-to + .note { + margin-top: 0.3em; } + +.mini-h-card { + border-radius: 2px; + border: 1px solid #586e75; + padding: 0 0.2em; + text-decoration: none; + margin-right: 5px; + white-space: nowrap; } + +.mini-h-card img { + height: 1.26em; + display: inline; + border-radius: 2px; + vertical-align: text-bottom; } + +.like-photo { + height: 1.26em; } + +.reply .e-content { + margin-top: 0.5em; + padding-left: 0.5em; } + +.notes-subtitle { + font-size: 1em; } + +.note-photo { + width: 100%; + height: auto; + image-orientation: from-image; } + +article header { + margin-top: 0.5em; + margin-bottom: 0.8em; } + +.post-info { + font-size: 0.8em; + font-style: italic; + margin-top: -0.8em; } + +.contact { + position: relative; } + +.contact-links { + list-style-type: none; } + +.contact img { + height: auto; + width: 2em; + position: absolute; + top: 0; + left: 0; } + +.contact-info { + margin-left: 2em; } + +#map { + height: 300px; } + +/* media queries */ +@media (min-width: 700px) { + main { + margin-left: 10em; + margin-right: 10em; } + footer { + margin-left: 13em; + margin-right: 13em; } + .youtube { + width: 640px; + height: 360px; } } + +@media (max-width: 699px) { + main { + margin-left: 10px; + margin-right: 10px; } + article { + word-wrap: break-word; } + footer { + margin-left: 15px; + margin-right: 15px; } + .youtube { + width: 100%; + height: auto; } } + +body { + text-rendering: optimizeLegibility; + -webkit-font-feature-settings: "liga"; + font-feature-settings: "liga"; + font-family: "leitura-news", serif; + font-size: 1.2em; } + +#topheader h1 { + font-family: "leitura-news", serif; } + +h1 { + font-family: "prenton", sans-serif; } + +#topheader a { + text-decoration: none; } + +nav { + -webkit-font-feature-settings: "dlig"; + font-feature-settings: "dlig"; } + +article header h1 a { + text-decoration: none; } + +article div a { + text-decoration: none; } + +footer { + font-size: 0.8em; } + +.emoji { + width: auto; + height: 1em; } + +body { + color: #002b36; } + +header a { + color: #002b36; } + +a { + color: #268bd2; } + +form { + width: 100%; } + +fieldset { + min-width: 0; + width: 100%; } + +input[type="text"], input[type="file"], textarea { + width: 100%; } + +input, button, textarea { + -webkit-appearance: none; + -moz-appearance: none; + background-color: #002b36; + color: #fdf6e3; + border: 1px solid #fdf6e3; + border-radius: 4px; } + +button:hover { + transition: 0.5s ease-in-out; + background-color: #fdf6e3; + color: #002b36; } + +button:disabled { + background-color: #93a1a1; + color: #002b36; } + +input[type="checkbox"] { + -webkit-appearance: checkbox; + -moz-appearance: checkbox; } + +#photo { + background: inherit; + color: inherit; + border: none; } + +.twitter-tweet-rendered { + margin-bottom: 0 !important; } + +.twitter-tweet-rendered + .note { + margin-top: 0; } + +/*# sourceMappingURL=global.css.map */ diff --git a/public/build/assets/css/global-ef9dfef096.css.br b/public/build/assets/css/global-ef9dfef096.css.br new file mode 100644 index 00000000..45595e73 Binary files /dev/null and b/public/build/assets/css/global-ef9dfef096.css.br differ diff --git a/public/build/assets/css/global-ef9dfef096.css.gz b/public/build/assets/css/global-ef9dfef096.css.gz new file mode 100644 index 00000000..1c6683bf Binary files /dev/null and b/public/build/assets/css/global-ef9dfef096.css.gz differ diff --git a/public/build/assets/css/global.css.map b/public/build/assets/css/global.css.map new file mode 100644 index 00000000..fa39372b --- /dev/null +++ b/public/build/assets/css/global.css.map @@ -0,0 +1 @@ +{"version":3,"sources":["global.scss","layout.scss","components/fonts.scss","components/colours.scss","components/forms.scss","components/twitter.scss"],"names":[],"mappings":"AAyBA;EACC,+CAAe,EACf;;AAED;EACC,cAAc,EACd;;AC5BD;EACC,uBAAuB,EACvB;;AAED;EACC,oBAAoB,EACpB;;AAED;EACC,sBAAc;EAAd,qBAAc;EAAd,cAAc;EACd,uBAAe;EAAf,mBAAe;EAAf,eAAe,EACf;;AAED;EACC,mBAAmB,EACnB;;AAED;EACC,mBAAmB,EACnB;;AAED;EACC,kBAAkB,EAClB;;AAED;EACC,0BDhBkB;ECiBlB,iCDlBkB;ECmBlB,qBAAqB;EACrB,gBAAgB,EAChB;;AAED;EACC,aAAa;EACb,eAAe;EACf,UAAU;EACV,YAAY,EACZ;;AAED;EACC,sBAAsB,EACtB;;AAED;EACC,cAAc,EACd;;AAED;EACC,YAAY,EACZ;;AAED;EACC,aAAa,EACb;;AAED;EACC,sBAAsB,EACtB;;AAED;EACC,YAAY;EACZ,YAAY;EACZ,cD7CkB,EC8ClB;;AAED;EACC,iBAAiB;EACjB,kBAAkB;EAClB,iBAAiB;EACjB,qBAAqB,EACrB;;AAED;EACC,iBAAiB;EACjB,kBAAkB;EAClB,iBAAiB;EACjB,iBAAiB,EACjB;;AAED;EACC,kBAAkB,EAClB;;AAED;EACC,mBAAmB;EACnB,0BD/EkB;ECgFlB,iBAAiB;EACjB,sBAAsB;EACtB,kBAAkB;EAClB,oBAAoB,EACpB;;AAED;EACC,eAAe;EACf,gBAAgB;EAChB,mBAAmB;EACnB,4BAA4B,EAC5B;;AAED;EACC,eAAe,EACf;;AAED;EACC,kBAAkB;EAClB,oBAAoB,EACpB;;AAED;EACC,eAAe,EACf;;AAED;EACC,YAAY;EACZ,aAAa;EACb,8BAA8B,EAC9B;;AAID;EACC,kBAAkB;EAClB,qBAAqB,EACrB;;AAED;EACC,iBAAiB;EACjB,mBAAmB;EACnB,mBAAmB,EACnB;;AAGD;EACC,mBAAmB,EACnB;;AAED;EACC,sBAAsB,EACtB;;AAED;EACC,aAAa;EACb,WAAW;EACX,mBAAmB;EACnB,OAAO;EACP,QAAQ,EACR;;AAED;EACC,iBAAiB,EACjB;;AAED;EACC,cAAc,EACd;;AAED,mBAAmB;AACnB;EACC;IACC,kBAAkB;IAClB,mBAAmB,EACnB;EAED;IACC,kBAAkB;IAClB,mBAAmB,EACnB;EAED;IACC,aAAa;IACb,cAAc,EACd,EAAA;;AAGF;EACC;IACC,kBAAkB;IAClB,mBAAmB,EACnB;EAED;IACC,sBAAsB,EACtB;EAED;IACC,kBAAkB;IAClB,mBAAmB,EACnB;EAED;IACC,YAAY;IACZ,aAAa,EACb,EAAA;;ACjMF;EACC,mCAAmC;EACnC,sCAA8B;EAA9B,8BAA8B;EAC9B,mCFFsC;EEGtC,iBAAiB,EACjB;;AAED;EACC,mCFPsC,EEQtC;;AAED;EACC,mCFVyC,EEWzC;;AAED;EACC,sBAAsB,EACtB;;AAED;EACC,sCAA8B;EAA9B,8BAA8B,EAC9B;;AAED;EACC,sBAAsB,EACtB;;AAED;EACC,sBAAsB,EACtB;;AAED;EACC,iBAAiB,EACjB;;AAED;EACC,YAAY;EACZ,YAAY,EACZ;;ACvCD;EACC,eHKkB,EGJlB;;AAED;EACC,eHCkB,EGAlB;;AAED;EACC,eHUkB,EGTlB;;ACTD;EACC,YAAY,EACZ;;AAED;EACC,aAAa;EACb,YAAY,EACZ;;AAED;EACC,YAAY,EACZ;;AAED;EACC,yBAAyB;EACzB,sBAAsB;EACtB,0BJXkB;EIYlB,eJLkB;EIMlB,0BJNkB;EIOlB,mBAAmB,EACnB;;AAED;EACC,6BAA6B;EAC7B,0BJZkB;EIalB,eJpBkB,EIqBlB;;AAED;EACC,0BJnBkB;EIoBlB,eJzBkB,EI0BlB;;AAED;EACC,6BAA6B;EAC7B,0BAA0B,EAC1B;;AAED;EACC,oBAAoB;EACpB,eAAe;EACf,aAAa,EACb;;AC1CD;EACE,4BAA4B,EAC7B;;AAED;EACE,cAAc,EACf","file":"global.css","sourcesContent":["//global.scss\n\n//variables\n$font-stack-body: \"leitura-news\", serif;\n$font-stack-headers: \"prenton\", sans-serif;\n\n//solarized variables TERMCOL\n$base03: #002b36;//brblack\n$base02: #073642;//black\n$base01: #586e75;//brgreen\n$base00: #657b83;//bryellow\n$base0: #839496;//brblue\n$base1: #93a1a1;//brcyan\n$base2: #eee8d5;//white\n$base3: #fdf6e3;//brwhite\n$yellow: #b58900;\n$orange: #cb4b16;\n$red: #dc322f;\n$magenta: #d33682;\n$violet: #6c71c4;\n$blue: #268bd2;\n$cyan: #2aa198;\n$green: #859900;\n\n//global styles\nhtml {\n\tbackground: url('/assets/img/escheresque.png');\n}\n\n.map {\n\theight: 150px;\n}\n\n//layout\n@import \"layout\";\n\n//components\n@import \"components/fonts\";\n@import \"components/colours\";\n@import \"components/forms\";\n@import \"components/twitter\";\n","//layout.scss\n\n//boxes\nhtml {\n\tbox-sizing: border-box;\n}\n\n*, *:before, *:after {\n\tbox-sizing: inherit;\n}\n\n#topheader {\n\tdisplay: flex;\n\tflex-flow: row;\n}\n\n#topheader a {\n\tpadding: 0.5em 1em;\n}\n\nnav {\n\tpadding-top: 0.5em;\n}\n\n.social-list {\n\tpadding-left: 2em;\n}\n\n.note {\n\tbackground-color: $base2;\n\tbox-shadow: 0 0 10px 2px $base1;\n\tpadding: 0.5em 0.5em;\n\tmargin-top: 1em;\n}\n\n.note:after {\n\tcontent: \" \";\n\tdisplay: block;\n\theight: 0;\n\tclear: both;\n}\n\n.note a {\n\tword-wrap: break-word;\n}\n\n.note .e-content p:first-child {\n\tmargin-top: 0;\n}\n\n.note-metadata {\n\twidth: 100%;\n}\n\n.social-links {\n\tfloat: right;\n}\n\n.social-links a {\n\ttext-decoration: none;\n}\n\n.icon {\n\twidth: auto;\n\theight: 1em;\n\tfill: $blue;\n}\n\n.reply {\n\tmargin-left: 2em;\n\tmargin-right: 2em;\n\tfont-size: 0.8em;\n\tpadding: 0.5em 0.5em;\n}\n\n.reply-to {\n\tmargin-left: 2em;\n\tmargin-right: 2em;\n\tfont-size: 0.8em;\n\tpadding-top: 2em;\n}\n\n.reply-to + .note {\n\tmargin-top: 0.3em;\n}\n\n.mini-h-card {\n\tborder-radius: 2px;\n\tborder: 1px solid $base01;\n\tpadding: 0 0.2em;\n\ttext-decoration: none;\n\tmargin-right: 5px;\n\twhite-space: nowrap;\n}\n\n.mini-h-card img {\n\theight: 1.26em;\n\tdisplay: inline;\n\tborder-radius: 2px;\n\tvertical-align: text-bottom;\n}\n\n.like-photo {\n\theight: 1.26em;\n}\n\n.reply .e-content {\n\tmargin-top: 0.5em;\n\tpadding-left: 0.5em;\n}\n\n.notes-subtitle {\n\tfont-size: 1em;\n}\n\n.note-photo {\n\twidth: 100%;\n\theight: auto;\n\timage-orientation: from-image;\n}\n\n//articles\n\narticle header {\n\tmargin-top: 0.5em;\n\tmargin-bottom: 0.8em;\n}\n\n.post-info {\n\tfont-size: 0.8em;\n\tfont-style: italic;\n\tmargin-top: -0.8em;\n}\n\n//contacts\n.contact {\n\tposition: relative;\n}\n\n.contact-links {\n\tlist-style-type: none;\n}\n\n.contact img {\n\theight: auto;\n\twidth: 2em;\n\tposition: absolute;\n\ttop: 0;\n\tleft: 0;\n}\n\n.contact-info {\n\tmargin-left: 2em;\n}\n\n#map {\n\theight: 300px;\n}\n\n/* media queries */\n@media (min-width: 700px) {\n\tmain {\n\t\tmargin-left: 10em;\n\t\tmargin-right: 10em;\n\t}\n\n\tfooter {\n\t\tmargin-left: 13em;\n\t\tmargin-right: 13em;\n\t}\n\n\t.youtube {\n\t\twidth: 640px;\n\t\theight: 360px;\n\t}\n}\n\n@media (max-width: 699px) {\n\tmain {\n\t\tmargin-left: 10px;\n\t\tmargin-right: 10px;\n\t}\n\n\tarticle {\n\t\tword-wrap: break-word;\n\t}\n\n\tfooter {\n\t\tmargin-left: 15px;\n\t\tmargin-right: 15px;\n\t}\n\n\t.youtube {\n\t\twidth: 100%;\n\t\theight: auto;\n\t}\n}\n","//fonts.scss\n\nbody {\n\ttext-rendering: optimizeLegibility;\n\tfont-feature-settings: \"liga\";\n\tfont-family: $font-stack-body;\n\tfont-size: 1.2em;\n}\n\n#topheader h1 {\n\tfont-family: $font-stack-body;\n}\n\nh1 {\n\tfont-family: $font-stack-headers;\n}\n\n#topheader a {\n\ttext-decoration: none;\n}\n\nnav {\n\tfont-feature-settings: \"dlig\";\n}\n\narticle header h1 a {\n\ttext-decoration: none;\n}\n\narticle div a {\n\ttext-decoration: none;\n}\n\nfooter {\n\tfont-size: 0.8em;\n}\n\n.emoji {\n\twidth: auto;\n\theight: 1em;\n}\n","//colours.scss\nbody {\n\tcolor: $base03;\n}\n\nheader a {\n\tcolor: $base03;\n}\n\na {\n\tcolor: $blue;\n}","//forms.scss\n\nform {\n\twidth: 100%;\n}\n\nfieldset {\n\tmin-width: 0;\n\twidth: 100%;\n}\n\ninput[type=\"text\"], input[type=\"file\"], textarea {\n\twidth: 100%;\n}\n\ninput, button, textarea {\n\t-webkit-appearance: none;\n\t-moz-appearance: none;\n\tbackground-color: $base03;\n\tcolor: $base3;\n\tborder: 1px solid $base3;\n\tborder-radius: 4px;\n}\n\nbutton:hover {\n\ttransition: 0.5s ease-in-out;\n\tbackground-color: $base3;\n\tcolor: $base03;\n}\n\nbutton:disabled {\n\tbackground-color: $base1;\n\tcolor: $base03;\n}\n\ninput[type=\"checkbox\"] {\n\t-webkit-appearance: checkbox;\n\t-moz-appearance: checkbox;\n}\n\n#photo {\n\tbackground: inherit;\n\tcolor: inherit;\n\tborder: none;\n}\n","//twitter.scss\n\n.twitter-tweet-rendered {\n margin-bottom: 0 !important;\n}\n\n.twitter-tweet-rendered + .note {\n margin-top: 0;\n}\n"],"sourceRoot":"/source/"} \ No newline at end of file diff --git a/public/build/assets/css/prism-5c98941a94.css b/public/build/assets/css/prism-5c98941a94.css new file mode 100644 index 00000000..86122e46 --- /dev/null +++ b/public/build/assets/css/prism-5c98941a94.css @@ -0,0 +1,188 @@ +/* http://prismjs.com/download.html?themes=prism-dark&languages=markup+css+clike+javascript+git+http+markdown+php+php-extras+scss+sql&plugins=line-numbers+show-invisibles */ +/** + * prism.js Dark theme for JavaScript, CSS and HTML + * Based on the slides of the talk “/Reg(exp){2}lained/” + * @author Lea Verou + */ + +code[class*="language-"], +pre[class*="language-"] { + color: white; + text-shadow: 0 -.1em .2em black; + font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace; + direction: ltr; + text-align: left; + white-space: pre; + word-spacing: normal; + word-break: normal; + line-height: 1.5; + + -moz-tab-size: 4; + -o-tab-size: 4; + tab-size: 4; + + -webkit-hyphens: none; + -moz-hyphens: none; + -ms-hyphens: none; + hyphens: none; +} + +@media print { + code[class*="language-"], + pre[class*="language-"] { + text-shadow: none; + } +} + +pre[class*="language-"], +:not(pre) > code[class*="language-"] { + background: hsl(30, 20%, 25%); +} + +/* Code blocks */ +pre[class*="language-"] { + padding: 1em; + margin: .5em 0; + overflow: auto; + border: .3em solid hsl(30, 20%, 40%); + border-radius: .5em; + box-shadow: 1px 1px .5em black inset; +} + +/* Inline code */ +:not(pre) > code[class*="language-"] { + padding: .15em .2em .05em; + border-radius: .3em; + border: .13em solid hsl(30, 20%, 40%); + box-shadow: 1px 1px .3em -.1em black inset; +} + +.token.comment, +.token.prolog, +.token.doctype, +.token.cdata { + color: hsl(30, 20%, 50%); +} + +.token.punctuation { + opacity: .7; +} + +.namespace { + opacity: .7; +} + +.token.property, +.token.tag, +.token.boolean, +.token.number, +.token.constant, +.token.symbol { + color: hsl(350, 40%, 70%); +} + +.token.selector, +.token.attr-name, +.token.string, +.token.char, +.token.builtin, +.token.inserted { + color: hsl(75, 70%, 60%); +} + +.token.operator, +.token.entity, +.token.url, +.language-css .token.string, +.style .token.string, +.token.variable { + color: hsl(40, 90%, 60%); +} + +.token.atrule, +.token.attr-value, +.token.keyword { + color: hsl(350, 40%, 70%); +} + +.token.regex, +.token.important { + color: #e90; +} + +.token.important, +.token.bold { + font-weight: bold; +} +.token.italic { + font-style: italic; +} + +.token.entity { + cursor: help; +} + +.token.deleted { + color: red; +} + +pre.line-numbers { + position: relative; + padding-left: 3.8em; + counter-reset: linenumber; +} + +pre.line-numbers > code { + position: relative; +} + +.line-numbers .line-numbers-rows { + position: absolute; + pointer-events: none; + top: 0; + font-size: 100%; + left: -3.8em; + width: 3em; /* works for line-numbers below 1000 lines */ + letter-spacing: -1px; + border-right: 1px solid #999; + + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + +} + + .line-numbers-rows > span { + pointer-events: none; + display: block; + counter-increment: linenumber; + } + + .line-numbers-rows > span:before { + content: counter(linenumber); + color: #999; + display: block; + padding-right: 0.8em; + text-align: right; + } +.token.tab:not(:empty):before, +.token.cr:before, +.token.lf:before { + color: hsl(24, 20%, 85%); +} + +.token.tab:not(:empty):before { + content: '\21E5'; +} + +.token.cr:before { + content: '\240D'; +} + +.token.crlf:before { + content: '\240D\240A'; +} +.token.lf:before { + content: '\240A'; +} diff --git a/public/build/assets/css/prism-5c98941a94.css.br b/public/build/assets/css/prism-5c98941a94.css.br new file mode 100644 index 00000000..101b9508 Binary files /dev/null and b/public/build/assets/css/prism-5c98941a94.css.br differ diff --git a/public/build/assets/css/prism-5c98941a94.css.gz b/public/build/assets/css/prism-5c98941a94.css.gz new file mode 100644 index 00000000..652d5e2b Binary files /dev/null and b/public/build/assets/css/prism-5c98941a94.css.gz differ diff --git a/public/build/assets/css/projects-d945298e4f.css b/public/build/assets/css/projects-d945298e4f.css new file mode 100644 index 00000000..d108175a --- /dev/null +++ b/public/build/assets/css/projects-d945298e4f.css @@ -0,0 +1,10 @@ +#projects { + padding-left: 33.33%; +} + +h3 { + float: left; + width: 45%; + margin: 0 5% 0 -50%; + text-align: right; +} diff --git a/public/build/assets/css/projects-d945298e4f.css.br b/public/build/assets/css/projects-d945298e4f.css.br new file mode 100644 index 00000000..1d6b0300 Binary files /dev/null and b/public/build/assets/css/projects-d945298e4f.css.br differ diff --git a/public/build/assets/css/projects-d945298e4f.css.gz b/public/build/assets/css/projects-d945298e4f.css.gz new file mode 100644 index 00000000..deca5763 Binary files /dev/null and b/public/build/assets/css/projects-d945298e4f.css.gz differ diff --git a/public/build/assets/css/sanitize.min-535bccd783.css b/public/build/assets/css/sanitize.min-535bccd783.css new file mode 100644 index 00000000..9d0d9800 --- /dev/null +++ b/public/build/assets/css/sanitize.min-535bccd783.css @@ -0,0 +1,2 @@ +/*! sanitize.css v3.2.0 | CC0 1.0 Public Domain | github.com/10up/sanitize.css */audio:not([controls]){display:none}button{-webkit-appearance:button;overflow:visible}details{display:block}html{-ms-overflow-style:-ms-autohiding-scrollbar;overflow-y:scroll;-webkit-text-size-adjust:100%}input{-webkit-border-radius:0}input[type=button],input[type=reset],input[type=submit]{-webkit-appearance:button}input[type=number]{width:auto}input[type=search]{-webkit-appearance:textfield}input[type=search]::-webkit-search-cancel-button,input[type=search]::-webkit-search-decoration{-webkit-appearance:none}main{display:block}pre{overflow:auto}progress{display:inline-block}small{font-size:75%}summary{display:block}svg:not(:root){overflow:hidden}template{display:none}textarea{overflow:auto}[hidden]{display:none}*,:after,:before{box-sizing:inherit}*{font-size:inherit;line-height:inherit}:after,:before{text-decoration:inherit;vertical-align:inherit}*,:after,:before{border-style:solid;border-width:0}*{background-repeat:no-repeat;margin:0;padding:0}:root{background-color:#fff;box-sizing:border-box;color:#000;cursor:default;font:100%/1.5 sans-serif}a{text-decoration:none}audio,canvas,iframe,img,svg,video{vertical-align:middle}button,input,select,textarea{background-color:transparent;color:inherit;font-family:inherit;font-style:inherit;font-weight:inherit}[type=button],[type=date],[type=datetime-local],[type=datetime],[type=email],[type=month],[type=number],[type=password],[type=reset],[type=search],[type=submit],[type=tel],[type=text],[type=time],[type=url],[type=week],button,select,textarea{min-height:1.5em}code,kbd,pre,samp{font-family:monospace}nav ol,nav ul{list-style:none}select{-moz-appearance:none;-webkit-appearance:none}select::-ms-expand{display:none}select::-ms-value{color:currentColor}table{border-collapse:collapse;border-spacing:0}textarea{resize:vertical}::-moz-selection{background-color:#b3d4fc;color:#fff;text-shadow:none}::selection{background-color:#b3d4fc;color:#fff;text-shadow:none}[aria-busy=true]{cursor:progress}[aria-controls]{cursor:pointer}[aria-disabled]{cursor:default}[hidden][aria-hidden=false]{clip:rect(0 0 0 0);display:inherit;position:absolute}[hidden][aria-hidden=false]:focus{clip:auto}[tabindex],a,area,button,input,label,select,textarea{-ms-touch-action:manipulation;touch-action:manipulation} +/*# sourceMappingURL=sanitize.min.css.map */ \ No newline at end of file diff --git a/public/build/assets/css/sanitize.min-535bccd783.css.br b/public/build/assets/css/sanitize.min-535bccd783.css.br new file mode 100644 index 00000000..f20400fd Binary files /dev/null and b/public/build/assets/css/sanitize.min-535bccd783.css.br differ diff --git a/public/build/assets/css/sanitize.min-535bccd783.css.gz b/public/build/assets/css/sanitize.min-535bccd783.css.gz new file mode 100644 index 00000000..ba3a709e Binary files /dev/null and b/public/build/assets/css/sanitize.min-535bccd783.css.gz differ diff --git a/public/build/assets/js/Autolinker.min-b46556773a.js b/public/build/assets/js/Autolinker.min-b46556773a.js new file mode 100644 index 00000000..e4f6366a --- /dev/null +++ b/public/build/assets/js/Autolinker.min-b46556773a.js @@ -0,0 +1,10 @@ +/*! + * Autolinker.js + * 0.24.0 + * + * Copyright(c) 2016 Gregory Jacobs + * MIT + * + * https://github.com/gregjacobs/Autolinker.js + */ +!function(a,b){"function"==typeof define&&define.amd?define([],function(){return a.Autolinker=b()}):"object"==typeof exports?module.exports=b():a.Autolinker=b()}(this,function(){var a=function(a){a=a||{},this.urls=this.normalizeUrlsCfg(a.urls),this.email="boolean"==typeof a.email?a.email:!0,this.twitter="boolean"==typeof a.twitter?a.twitter:!0,this.phone="boolean"==typeof a.phone?a.phone:!0,this.hashtag=a.hashtag||!1,this.newWindow="boolean"==typeof a.newWindow?a.newWindow:!0,this.stripPrefix="boolean"==typeof a.stripPrefix?a.stripPrefix:!0;var b=this.hashtag;if(b!==!1&&"twitter"!==b&&"facebook"!==b&&"instagram"!==b)throw new Error("invalid `hashtag` cfg - see docs");this.truncate=this.normalizeTruncateCfg(a.truncate),this.className=a.className||"",this.replaceFn=a.replaceFn||null,this.htmlParser=null,this.matchers=null,this.tagBuilder=null};return a.prototype={constructor:a,normalizeUrlsCfg:function(a){return null==a&&(a=!0),"boolean"==typeof a?{schemeMatches:a,wwwMatches:a,tldMatches:a}:{schemeMatches:"boolean"==typeof a.schemeMatches?a.schemeMatches:!0,wwwMatches:"boolean"==typeof a.wwwMatches?a.wwwMatches:!0,tldMatches:"boolean"==typeof a.tldMatches?a.tldMatches:!0}},normalizeTruncateCfg:function(b){return"number"==typeof b?{length:b,location:"end"}:a.Util.defaults(b||{},{length:Number.POSITIVE_INFINITY,location:"end"})},parse:function(a){for(var b=this.getHtmlParser(),c=b.parse(a),d=0,e=[],f=0,g=c.length;g>f;f++){var h=c[f],i=h.getType();if("element"===i&&"a"===h.getTagName())h.isClosing()?d=Math.max(d-1,0):d++;else if("text"===i&&0===d){var j=this.parseText(h.getText(),h.getOffset());e.push.apply(e,j)}}return e=this.compactMatches(e),this.hashtag||(e=e.filter(function(a){return"hashtag"!==a.getType()})),this.email||(e=e.filter(function(a){return"email"!==a.getType()})),this.phone||(e=e.filter(function(a){return"phone"!==a.getType()})),this.twitter||(e=e.filter(function(a){return"twitter"!==a.getType()})),this.urls.schemeMatches||(e=e.filter(function(a){return"url"!==a.getType()||"scheme"!==a.getUrlMatchType()})),this.urls.wwwMatches||(e=e.filter(function(a){return"url"!==a.getType()||"www"!==a.getUrlMatchType()})),this.urls.tldMatches||(e=e.filter(function(a){return"url"!==a.getType()||"tld"!==a.getUrlMatchType()})),e},compactMatches:function(a){a.sort(function(a,b){return a.getOffset()-b.getOffset()});for(var b=0;be;e++){for(var g=c[e].parseMatches(a),h=0,i=g.length;i>h;h++)g[h].setOffset(b+g[h].getOffset());d.push.apply(d,g)}return d},link:function(a){if(!a)return"";for(var b=this.parse(a),c=[],d=0,e=0,f=b.length;f>e;e++){var g=b[e];c.push(a.substring(d,g.getOffset())),c.push(this.createMatchReturnVal(g)),d=g.getOffset()+g.getMatchedText().length}return c.push(a.substring(d)),c.join("")},createMatchReturnVal:function(b){var c;if(this.replaceFn&&(c=this.replaceFn.call(this,this,b)),"string"==typeof c)return c;if(c===!1)return b.getMatchedText();if(c instanceof a.HtmlTag)return c.toAnchorString();var d=this.getTagBuilder(),e=d.build(b);return e.toAnchorString()},getHtmlParser:function(){var b=this.htmlParser;return b||(b=this.htmlParser=new a.htmlParser.HtmlParser),b},getMatchers:function(){if(this.matchers)return this.matchers;var b=a.matcher,c=[new b.Hashtag({serviceName:this.hashtag}),new b.Email,new b.Phone,new b.Twitter,new b.Url({stripPrefix:this.stripPrefix})];return this.matchers=c},getTagBuilder:function(){var b=this.tagBuilder;return b||(b=this.tagBuilder=new a.AnchorTagBuilder({newWindow:this.newWindow,truncate:this.truncate,className:this.className})),b}},a.link=function(b,c){var d=new a(c);return d.link(b)},a.match={},a.matcher={},a.htmlParser={},a.truncate={},a.Util={abstractMethod:function(){throw"abstract"},trimRegex:/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,assign:function(a,b){for(var c in b)b.hasOwnProperty(c)&&(a[c]=b[c]);return a},defaults:function(a,b){for(var c in b)b.hasOwnProperty(c)&&void 0===a[c]&&(a[c]=b[c]);return a},extend:function(b,c){var d=b.prototype,e=function(){};e.prototype=d;var f;f=c.hasOwnProperty("constructor")?c.constructor:function(){d.constructor.apply(this,arguments)};var g=f.prototype=new e;return g.constructor=f,g.superclass=d,delete c.constructor,a.Util.assign(g,c),f},ellipsis:function(a,b,c){return a.length>b&&(c=null==c?"..":c,a=a.substring(0,b-c.length)+c),a},indexOf:function(a,b){if(Array.prototype.indexOf)return a.indexOf(b);for(var c=0,d=a.length;d>c;c++)if(a[c]===b)return c;return-1},splitAndCapture:function(a,b){if(!b.global)throw new Error("`splitRegex` must have the 'g' flag set");for(var c,d=[],e=0;c=b.exec(a);)d.push(a.substring(e,c.index)),d.push(c[0]),e=c.index+c[0].length;return d.push(a.substring(e)),d},trim:function(a){return a.replace(this.trimRegex,"")}},a.HtmlTag=a.Util.extend(Object,{whitespaceRegex:/\s+/,constructor:function(b){a.Util.assign(this,b),this.innerHtml=this.innerHtml||this.innerHTML},setTagName:function(a){return this.tagName=a,this},getTagName:function(){return this.tagName||""},setAttr:function(a,b){var c=this.getAttrs();return c[a]=b,this},getAttr:function(a){return this.getAttrs()[a]},setAttrs:function(b){var c=this.getAttrs();return a.Util.assign(c,b),this},getAttrs:function(){return this.attrs||(this.attrs={})},setClass:function(a){return this.setAttr("class",a)},addClass:function(b){for(var c,d=this.getClass(),e=this.whitespaceRegex,f=a.Util.indexOf,g=d?d.split(e):[],h=b.split(e);c=h.shift();)-1===f(g,c)&&g.push(c);return this.getAttrs()["class"]=g.join(" "),this},removeClass:function(b){for(var c,d=this.getClass(),e=this.whitespaceRegex,f=a.Util.indexOf,g=d?d.split(e):[],h=b.split(e);g.length&&(c=h.shift());){var i=f(g,c);-1!==i&&g.splice(i,1)}return this.getAttrs()["class"]=g.join(" "),this},getClass:function(){return this.getAttrs()["class"]||""},hasClass:function(a){return-1!==(" "+this.getClass()+" ").indexOf(" "+a+" ")},setInnerHtml:function(a){return this.innerHtml=a,this},getInnerHtml:function(){return this.innerHtml||""},toAnchorString:function(){var a=this.getTagName(),b=this.buildAttrsStr();return b=b?" "+b:"",["<",a,b,">",this.getInnerHtml(),""].join("")},buildAttrsStr:function(){if(!this.attrs)return"";var a=this.getAttrs(),b=[];for(var c in a)a.hasOwnProperty(c)&&b.push(c+'="'+a[c]+'"');return b.join(" ")}}),a.RegexLib=function(){var a="A-Za-zªµºÀ-ÖØ-öø-ˁˆ-ˑˠ-ˤˬˮͰ-ʹͶͷͺ-ͽͿΆΈ-ΊΌΎ-ΡΣ-ϵϷ-ҁҊ-ԯԱ-Ֆՙա-ևא-תװ-ײؠ-يٮٯٱ-ۓەۥۦۮۯۺ-ۼۿܐܒ-ܯݍ-ޥޱߊ-ߪߴߵߺࠀ-ࠕࠚࠤࠨࡀ-ࡘࢠ-ࢴऄ-हऽॐक़-ॡॱ-ঀঅ-ঌএঐও-নপ-রলশ-হঽৎড়ঢ়য়-ৡৰৱਅ-ਊਏਐਓ-ਨਪ-ਰਲਲ਼ਵਸ਼ਸਹਖ਼-ੜਫ਼ੲ-ੴઅ-ઍએ-ઑઓ-નપ-રલળવ-હઽૐૠૡૹଅ-ଌଏଐଓ-ନପ-ରଲଳଵ-ହଽଡ଼ଢ଼ୟ-ୡୱஃஅ-ஊஎ-ஐஒ-கஙசஜஞடணதந-பம-ஹௐఅ-ఌఎ-ఐఒ-నప-హఽౘ-ౚౠౡಅ-ಌಎ-ಐಒ-ನಪ-ಳವ-ಹಽೞೠೡೱೲഅ-ഌഎ-ഐഒ-ഺഽൎൟ-ൡൺ-ൿඅ-ඖක-නඳ-රලව-ෆก-ะาำเ-ๆກຂຄງຈຊຍດ-ທນ-ຟມ-ຣລວສຫອ-ະາຳຽເ-ໄໆໜ-ໟༀཀ-ཇཉ-ཬྈ-ྌက-ဪဿၐ-ၕၚ-ၝၡၥၦၮ-ၰၵ-ႁႎႠ-ჅჇჍა-ჺჼ-ቈቊ-ቍቐ-ቖቘቚ-ቝበ-ኈኊ-ኍነ-ኰኲ-ኵኸ-ኾዀዂ-ዅወ-ዖዘ-ጐጒ-ጕጘ-ፚᎀ-ᎏᎠ-Ᏽᏸ-ᏽᐁ-ᙬᙯ-ᙿᚁ-ᚚᚠ-ᛪᛱ-ᛸᜀ-ᜌᜎ-ᜑᜠ-ᜱᝀ-ᝑᝠ-ᝬᝮ-ᝰក-ឳៗៜᠠ-ᡷᢀ-ᢨᢪᢰ-ᣵᤀ-ᤞᥐ-ᥭᥰ-ᥴᦀ-ᦫᦰ-ᧉᨀ-ᨖᨠ-ᩔᪧᬅ-ᬳᭅ-ᭋᮃ-ᮠᮮᮯᮺ-ᯥᰀ-ᰣᱍ-ᱏᱚ-ᱽᳩ-ᳬᳮ-ᳱᳵᳶᴀ-ᶿḀ-ἕἘ-Ἕἠ-ὅὈ-Ὅὐ-ὗὙὛὝὟ-ώᾀ-ᾴᾶ-ᾼιῂ-ῄῆ-ῌῐ-ΐῖ-Ίῠ-Ῥῲ-ῴῶ-ῼⁱⁿₐ-ₜℂℇℊ-ℓℕℙ-ℝℤΩℨK-ℭℯ-ℹℼ-ℿⅅ-ⅉⅎↃↄⰀ-Ⱞⰰ-ⱞⱠ-ⳤⳫ-ⳮⳲⳳⴀ-ⴥⴧⴭⴰ-ⵧⵯⶀ-ⶖⶠ-ⶦⶨ-ⶮⶰ-ⶶⶸ-ⶾⷀ-ⷆⷈ-ⷎⷐ-ⷖⷘ-ⷞⸯ々〆〱-〵〻〼ぁ-ゖゝ-ゟァ-ヺー-ヿㄅ-ㄭㄱ-ㆎㆠ-ㆺㇰ-ㇿ㐀-䶵一-鿕ꀀ-ꒌꓐ-ꓽꔀ-ꘌꘐ-ꘟꘪꘫꙀ-ꙮꙿ-ꚝꚠ-ꛥꜗ-ꜟꜢ-ꞈꞋ-ꞭꞰ-ꞷꟷ-ꠁꠃ-ꠅꠇ-ꠊꠌ-ꠢꡀ-ꡳꢂ-ꢳꣲ-ꣷꣻꣽꤊ-ꤥꤰ-ꥆꥠ-ꥼꦄ-ꦲꧏꧠ-ꧤꧦ-ꧯꧺ-ꧾꨀ-ꨨꩀ-ꩂꩄ-ꩋꩠ-ꩶꩺꩾ-ꪯꪱꪵꪶꪹ-ꪽꫀꫂꫛ-ꫝꫠ-ꫪꫲ-ꫴꬁ-ꬆꬉ-ꬎꬑ-ꬖꬠ-ꬦꬨ-ꬮꬰ-ꭚꭜ-ꭥꭰ-ꯢ가-힣ힰ-ퟆퟋ-ퟻ豈-舘並-龎ff-stﬓ-ﬗיִײַ-ﬨשׁ-זּטּ-לּמּנּסּףּפּצּ-ﮱﯓ-ﴽﵐ-ﶏﶒ-ﷇﷰ-ﷻﹰ-ﹴﹶ-ﻼA-Za-zヲ-하-ᅦᅧ-ᅬᅭ-ᅲᅳ-ᅵ",b="0-9٠-٩۰-۹߀-߉०-९০-৯੦-੯૦-૯୦-୯௦-௯౦-౯೦-೯൦-൯෦-෯๐-๙໐-໙༠-༩၀-၉႐-႙០-៩᠐-᠙᥆-᥏᧐-᧙᪀-᪉᪐-᪙᭐-᭙᮰-᮹᱀-᱉᱐-᱙꘠-꘩꣐-꣙꤀-꤉꧐-꧙꧰-꧹꩐-꩙꯰-꯹0-9",c=a+b,d=new RegExp("["+c+".\\-]*["+c+"\\-]"),e=/(?:international|construction|contractors|enterprises|photography|productions|foundation|immobilien|industries|management|properties|technology|christmas|community|directory|education|equipment|institute|marketing|solutions|vacations|bargains|boutique|builders|catering|cleaning|clothing|computer|democrat|diamonds|graphics|holdings|lighting|partners|plumbing|supplies|training|ventures|academy|careers|company|cruises|domains|exposed|flights|florist|gallery|guitars|holiday|kitchen|neustar|okinawa|recipes|rentals|reviews|shiksha|singles|support|systems|agency|berlin|camera|center|coffee|condos|dating|estate|events|expert|futbol|kaufen|luxury|maison|monash|museum|nagoya|photos|repair|report|social|supply|tattoo|tienda|travel|viajes|villas|vision|voting|voyage|actor|build|cards|cheap|codes|dance|email|glass|house|mango|ninja|parts|photo|press|shoes|solar|today|tokyo|tools|watch|works|aero|arpa|asia|best|bike|blue|buzz|camp|club|cool|coop|farm|fish|gift|guru|info|jobs|kiwi|kred|land|limo|link|menu|mobi|moda|name|pics|pink|post|qpon|rich|ruhr|sexy|tips|vote|voto|wang|wien|wiki|zone|bar|bid|biz|cab|cat|ceo|com|edu|gov|int|kim|mil|net|onl|org|pro|pub|red|tel|uno|wed|xxx|xyz|ac|ad|ae|af|ag|ai|al|am|an|ao|aq|ar|as|at|au|aw|ax|az|ba|bb|bd|be|bf|bg|bh|bi|bj|bm|bn|bo|br|bs|bt|bv|bw|by|bz|ca|cc|cd|cf|cg|ch|ci|ck|cl|cm|cn|co|cr|cu|cv|cw|cx|cy|cz|de|dj|dk|dm|do|dz|ec|ee|eg|er|es|et|eu|fi|fj|fk|fm|fo|fr|ga|gb|gd|ge|gf|gg|gh|gi|gl|gm|gn|gp|gq|gr|gs|gt|gu|gw|gy|hk|hm|hn|hr|ht|hu|id|ie|il|im|in|io|iq|ir|is|it|je|jm|jo|jp|ke|kg|kh|ki|km|kn|kp|kr|kw|ky|kz|la|lb|lc|li|lk|lr|ls|lt|lu|lv|ly|ma|mc|md|me|mg|mh|mk|ml|mm|mn|mo|mp|mq|mr|ms|mt|mu|mv|mw|mx|my|mz|na|nc|ne|nf|ng|ni|nl|no|np|nr|nu|nz|om|pa|pe|pf|pg|ph|pk|pl|pm|pn|pr|ps|pt|pw|py|qa|re|ro|rs|ru|rw|sa|sb|sc|sd|se|sg|sh|si|sj|sk|sl|sm|sn|so|sr|st|su|sv|sx|sy|sz|tc|td|tf|tg|th|tj|tk|tl|tm|tn|to|tp|tr|tt|tv|tw|tz|ua|ug|uk|us|uy|uz|va|vc|ve|vg|vi|vn|vu|wf|ws|ye|yt|za|zm|zw)\b/;return{alphaNumericCharsStr:c,domainNameRegex:d,tldRegex:e}}(),a.AnchorTagBuilder=a.Util.extend(Object,{constructor:function(b){a.Util.assign(this,b)},build:function(b){return new a.HtmlTag({tagName:"a",attrs:this.createAttrs(b.getType(),b.getAnchorHref()),innerHtml:this.processAnchorText(b.getAnchorText())})},createAttrs:function(a,b){var c={href:b},d=this.createCssClass(a);return d&&(c["class"]=d),this.newWindow&&(c.target="_blank"),c},createCssClass:function(a){var b=this.className;return b?b+" "+b+"-"+a:""},processAnchorText:function(a){return a=this.doTruncate(a)},doTruncate:function(b){var c=this.truncate;if(!c)return b;var d=c.length,e=c.location;return"smart"===e?a.truncate.TruncateSmart(b,d,".."):"middle"===e?a.truncate.TruncateMiddle(b,d,".."):a.truncate.TruncateEnd(b,d,"..")}}),a.htmlParser.HtmlParser=a.Util.extend(Object,{htmlRegex:function(){var a=/!--([\s\S]+?)--/,b=/[0-9a-zA-Z][0-9a-zA-Z:]*/,c=/[^\s\0"'>\/=\x01-\x1F\x7F]+/,d=/(?:"[^"]*?"|'[^']*?'|[^'"=<>`\s]+)/,e=c.source+"(?:\\s*=\\s*"+d.source+")?";return new RegExp(["(?:","<(!DOCTYPE)","(?:","\\s+","(?:",e,"|",d.source+")",")*",">",")","|","(?:","<(/)?","(?:",a.source,"|","(?:","("+b.source+")","(?:","\\s+",e,")*","\\s*/?",")",")",">",")"].join(""),"gi")}(),htmlCharacterEntitiesRegex:/( | |<|<|>|>|"|"|')/gi,parse:function(a){for(var b,c,d=this.htmlRegex,e=0,f=[];null!==(b=d.exec(a));){var g=b[0],h=b[3],i=b[1]||b[4],j=!!b[2],k=b.index,l=a.substring(e,k);l&&(c=this.parseTextAndEntityNodes(e,l),f.push.apply(f,c)),f.push(h?this.createCommentNode(k,g,h):this.createElementNode(k,g,i,j)),e=k+g.length}if(ef;f+=2){var h=e[f],i=e[f+1];h&&(d.push(this.createTextNode(b,h)),b+=h.length),i&&(d.push(this.createEntityNode(b,i)),b+=i.length)}return d},createCommentNode:function(b,c,d){return new a.htmlParser.CommentNode({offset:b,text:c,comment:a.Util.trim(d)})},createElementNode:function(b,c,d,e){return new a.htmlParser.ElementNode({offset:b,text:c,tagName:d.toLowerCase(),closing:e})},createEntityNode:function(b,c){return new a.htmlParser.EntityNode({offset:b,text:c})},createTextNode:function(b,c){return new a.htmlParser.TextNode({offset:b,text:c})}}),a.htmlParser.HtmlNode=a.Util.extend(Object,{offset:void 0,text:void 0,constructor:function(b){if(a.Util.assign(this,b),null==this.offset)throw new Error("`offset` cfg required");if(null==this.text)throw new Error("`text` cfg required")},getType:a.Util.abstractMethod,getOffset:function(){return this.offset},getText:function(){return this.text}}),a.htmlParser.CommentNode=a.Util.extend(a.htmlParser.HtmlNode,{comment:"",getType:function(){return"comment"},getComment:function(){return this.comment}}),a.htmlParser.ElementNode=a.Util.extend(a.htmlParser.HtmlNode,{tagName:"",closing:!1,getType:function(){return"element"},getTagName:function(){return this.tagName},isClosing:function(){return this.closing}}),a.htmlParser.EntityNode=a.Util.extend(a.htmlParser.HtmlNode,{getType:function(){return"entity"}}),a.htmlParser.TextNode=a.Util.extend(a.htmlParser.HtmlNode,{getType:function(){return"text"}}),a.match.Match=a.Util.extend(Object,{constructor:function(a,b){if(null==a)throw new Error("`matchedText` arg required");if(null==b)throw new Error("`offset` arg required");this.matchedText=a,this.offset=b},getType:a.Util.abstractMethod,getMatchedText:function(){return this.matchedText},setOffset:function(a){this.offset=a},getOffset:function(){return this.offset},getAnchorHref:a.Util.abstractMethod,getAnchorText:a.Util.abstractMethod}),a.match.Email=a.Util.extend(a.match.Match,{constructor:function(b,c,d){if(a.match.Match.prototype.constructor.call(this,b,c),!d)throw new Error("`email` arg required");this.email=d},getType:function(){return"email"},getEmail:function(){return this.email},getAnchorHref:function(){return"mailto:"+this.email},getAnchorText:function(){return this.email}}),a.match.Hashtag=a.Util.extend(a.match.Match,{constructor:function(b,c,d,e){if(a.match.Match.prototype.constructor.call(this,b,c),!e)throw new Error("`hashtag` arg required");this.serviceName=d,this.hashtag=e},getType:function(){return"hashtag"},getServiceName:function(){return this.serviceName},getHashtag:function(){return this.hashtag},getAnchorHref:function(){var a=this.serviceName,b=this.hashtag;switch(a){case"twitter":return"https://twitter.com/hashtag/"+b;case"facebook":return"https://www.facebook.com/hashtag/"+b;case"instagram":return"https://instagram.com/explore/tags/"+b;default:throw new Error("Unknown service name to point hashtag to: ",a)}},getAnchorText:function(){return"#"+this.hashtag}}),a.match.Phone=a.Util.extend(a.match.Match,{constructor:function(b,c,d,e){if(a.match.Match.prototype.constructor.call(this,b,c),!d)throw new Error("`number` arg required");if(null==e)throw new Error("`plusSign` arg required");this.number=d,this.plusSign=e},getType:function(){return"phone"},getNumber:function(){return this.number},getAnchorHref:function(){return"tel:"+(this.plusSign?"+":"")+this.number},getAnchorText:function(){return this.matchedText}}),a.match.Twitter=a.Util.extend(a.match.Match,{constructor:function(b,c,d){if(a.match.Match.prototype.constructor.call(this,b,c),!d)throw new Error("`twitterHandle` arg required");this.twitterHandle=d},getType:function(){return"twitter"},getTwitterHandle:function(){return this.twitterHandle},getAnchorHref:function(){return"https://twitter.com/"+this.twitterHandle},getAnchorText:function(){return"@"+this.twitterHandle}}),a.match.Url=a.Util.extend(a.match.Match,{constructor:function(b,c,d,e,f,g,h){if(a.match.Match.prototype.constructor.call(this,b,c),"scheme"!==e&&"www"!==e&&"tld"!==e)throw new Error('`urlMatchType` must be one of: "scheme", "www", or "tld"');if(!d)throw new Error("`url` arg required");if(null==f)throw new Error("`protocolUrlMatch` arg required");if(null==g)throw new Error("`protocolRelativeMatch` arg required");if(null==h)throw new Error("`stripPrefix` arg required");this.urlMatchType=e,this.url=d,this.protocolUrlMatch=f,this.protocolRelativeMatch=g,this.stripPrefix=h},urlPrefixRegex:/^(https?:\/\/)?(www\.)?/i,protocolRelativeRegex:/^\/\//,protocolPrepended:!1,getType:function(){return"url"},getUrlMatchType:function(){return this.urlMatchType},getUrl:function(){var a=this.url;return this.protocolRelativeMatch||this.protocolUrlMatch||this.protocolPrepended||(a=this.url="http://"+a,this.protocolPrepended=!0),a},getAnchorHref:function(){var a=this.getUrl();return a.replace(/&/g,"&")},getAnchorText:function(){var a=this.getMatchedText();return this.protocolRelativeMatch&&(a=this.stripProtocolRelativePrefix(a)),this.stripPrefix&&(a=this.stripUrlPrefix(a)),a=this.removeTrailingSlash(a)},stripUrlPrefix:function(a){return a.replace(this.urlPrefixRegex,"")},stripProtocolRelativePrefix:function(a){return a.replace(this.protocolRelativeRegex,"")},removeTrailingSlash:function(a){return"/"===a.charAt(a.length-1)&&(a=a.slice(0,-1)),a}}),a.matcher.Matcher=a.Util.extend(Object,{constructor:function(b){a.Util.assign(this,b)},parseMatches:a.Util.abstractMethod}),a.matcher.Email=a.Util.extend(a.matcher.Matcher,{matcherRegex:function(){var b=a.RegexLib.alphaNumericCharsStr,c=new RegExp("["+b+"\\-;:&=+$.,]+@"),d=a.RegexLib.domainNameRegex,e=a.RegexLib.tldRegex;return new RegExp([c.source,d.source,"\\.",e.source].join(""),"gi")}(),parseMatches:function(b){for(var c,d=this.matcherRegex,e=[];null!==(c=d.exec(b));){var f=c[0];e.push(new a.match.Email(f,c.index,f))}return e}}),a.matcher.Hashtag=a.Util.extend(a.matcher.Matcher,{matcherRegex:new RegExp("#[_"+a.RegexLib.alphaNumericCharsStr+"]{1,139}","g"),nonWordCharRegex:new RegExp("[^"+a.RegexLib.alphaNumericCharsStr+"]"),constructor:function(){a.matcher.Matcher.prototype.constructor.apply(this,arguments)},parseMatches:function(b){for(var c,d=this.matcherRegex,e=this.nonWordCharRegex,f=this.serviceName,g=[];null!==(c=d.exec(b));){var h=c.index,i=b.charAt(h-1);if(0===h||e.test(i)){var j=c[0],k=c[0].slice(1);g.push(new a.match.Hashtag(j,h,f,k))}}return g}}),a.matcher.Phone=a.Util.extend(a.matcher.Matcher,{matcherRegex:/(?:(\+)?\d{1,3}[-\040.])?\(?\d{3}\)?[-\040.]?\d{3}[-\040.]\d{4}/g,parseMatches:function(b){for(var c,d=this.matcherRegex,e=[];null!==(c=d.exec(b));){var f=c[0],g=f.replace(/\D/g,""),h=!!c[1];e.push(new a.match.Phone(f,c.index,g,h))}return e}}),a.matcher.Twitter=a.Util.extend(a.matcher.Matcher,{matcherRegex:new RegExp("@[_"+a.RegexLib.alphaNumericCharsStr+"]{1,20}","g"),nonWordCharRegex:new RegExp("[^"+a.RegexLib.alphaNumericCharsStr+"]"),parseMatches:function(b){for(var c,d=this.matcherRegex,e=this.nonWordCharRegex,f=[];null!==(c=d.exec(b));){var g=c.index,h=b.charAt(g-1);if(0===g||e.test(h)){var i=c[0],j=c[0].slice(1);f.push(new a.match.Twitter(i,g,j))}}return f}}),a.matcher.Url=a.Util.extend(a.matcher.Matcher,{matcherRegex:function(){var b=/(?:[A-Za-z][-.+A-Za-z0-9]*:(?![A-Za-z][-.+A-Za-z0-9]*:\/\/)(?!\d+\/?)(?:\/\/)?)/,c=/(?:www\.)/,d=a.RegexLib.domainNameRegex,e=a.RegexLib.tldRegex,f=a.RegexLib.alphaNumericCharsStr,g=new RegExp("["+f+"\\-+&@#/%=~_()|'$*\\[\\]?!:,.;]*["+f+"\\-+&@#/%=~_()|'$*\\[\\]]");return new RegExp(["(?:","(",b.source,d.source,")","|","(","(//)?",c.source,d.source,")","|","(","(//)?",d.source+"\\.",e.source,")",")","(?:"+g.source+")?"].join(""),"gi")}(),wordCharRegExp:/\w/,openParensRe:/\(/g,closeParensRe:/\)/g,constructor:function(){if(a.matcher.Matcher.prototype.constructor.apply(this,arguments),null==this.stripPrefix)throw new Error("`stripPrefix` cfg required")},parseMatches:function(b){for(var c,d=this.matcherRegex,e=this.stripPrefix,f=[];null!==(c=d.exec(b));){var g=c[0],h=c[1],i=c[2],j=c[3],k=c[5],l=c.index,m=j||k,n=b.charAt(l-1);if(a.matcher.UrlMatchValidator.isValid(g,h)&&!(l>0&&"@"===n||l>0&&m&&this.wordCharRegExp.test(n))){if(this.matchHasUnbalancedClosingParen(g))g=g.substr(0,g.length-1);else{var o=this.matchHasInvalidCharAfterTld(g,h);o>-1&&(g=g.substr(0,o))}var p=h?"scheme":i?"www":"tld",q=!!h;f.push(new a.match.Url(g,l,g,p,q,!!m,e))}}return f},matchHasUnbalancedClosingParen:function(a){var b=a.charAt(a.length-1);if(")"===b){var c=a.match(this.openParensRe),d=a.match(this.closeParensRe),e=c&&c.length||0,f=d&&d.length||0;if(f>e)return!0}return!1},matchHasInvalidCharAfterTld:function(a,b){if(!a)return-1;var c=0;b&&(c=a.indexOf(":"),a=a.slice(c));var d=/^((.?\/\/)?[A-Za-z0-9\u00C0-\u017F\.\-]*[A-Za-z0-9\u00C0-\u017F\-]\.[A-Za-z]+)/,e=d.exec(a);return null===e?-1:(c+=e[1].length,a=a.slice(e[1].length),/^[^.A-Za-z:\/?#]/.test(a)?c:-1)}}),a.matcher.UrlMatchValidator={hasFullProtocolRegex:/^[A-Za-z][-.+A-Za-z0-9]*:\/\//,uriSchemeRegex:/^[A-Za-z][-.+A-Za-z0-9]*:/,hasWordCharAfterProtocolRegex:/:[^\s]*?[A-Za-z\u00C0-\u017F]/,isValid:function(a,b){return b&&!this.isValidUriScheme(b)||this.urlMatchDoesNotHaveProtocolOrDot(a,b)||this.urlMatchDoesNotHaveAtLeastOneWordChar(a,b)?!1:!0},isValidUriScheme:function(a){var b=a.match(this.uriSchemeRegex)[0].toLowerCase();return"javascript:"!==b&&"vbscript:"!==b},urlMatchDoesNotHaveProtocolOrDot:function(a,b){return!(!a||b&&this.hasFullProtocolRegex.test(b)||-1!==a.indexOf("."))},urlMatchDoesNotHaveAtLeastOneWordChar:function(a,b){return a&&b?!this.hasWordCharAfterProtocolRegex.test(a):!1}},a.truncate.TruncateEnd=function(b,c,d){return a.Util.ellipsis(b,c,d)},a.truncate.TruncateMiddle=function(a,b,c){if(a.length<=b)return a;var d=b-c.length,e="";return d>0&&(e=a.substr(-1*Math.floor(d/2))),(a.substr(0,Math.ceil(d/2))+c+e).substr(0,b)},a.truncate.TruncateSmart=function(a,b,c){var d=function(a){var b={},c=a,d=c.match(/^([a-z]+):\/\//i);return d&&(b.scheme=d[1],c=c.substr(d[0].length)),d=c.match(/^(.*?)(?=(\?|#|\/|$))/i),d&&(b.host=d[1],c=c.substr(d[0].length)),d=c.match(/^\/(.*?)(?=(\?|#|$))/i),d&&(b.path=d[1],c=c.substr(d[0].length)),d=c.match(/^\?(.*?)(?=(#|$))/i),d&&(b.query=d[1],c=c.substr(d[0].length)),d=c.match(/^#(.*?)$/i),d&&(b.fragment=d[1]),b},e=function(a){var b="";return a.scheme&&a.host&&(b+=a.scheme+"://"),a.host&&(b+=a.host),a.path&&(b+="/"+a.path),a.query&&(b+="?"+a.query),a.fragment&&(b+="#"+a.fragment),b},f=function(a,b){var d=b/2,e=Math.ceil(d),f=-1*Math.floor(d),g="";return 0>f&&(g=a.substr(f)),a.substr(0,e)+c+g};if(a.length<=b)return a;var g=b-c.length,h=d(a);if(h.query){var i=h.query.match(/^(.*?)(?=(\?|\#))(.*?)$/i);i&&(h.query=h.query.substr(0,i[1].length),a=e(h))}if(a.length<=b)return a;if(h.host&&(h.host=h.host.replace(/^www\./,""),a=e(h)),a.length<=b)return a;var j="";if(h.host&&(j+=h.host),j.length>=g)return h.host.length==b?(h.host.substr(0,b-c.length)+c).substr(0,b):f(j,g).substr(0,b);var k="";if(h.path&&(k+="/"+h.path),h.query&&(k+="?"+h.query),k){if((j+k).length>=g){if((j+k).length==b)return(j+k).substr(0,b);var l=g-j.length;return(j+f(k,l)).substr(0,b)}j+=k}if(h.fragment){var m="#"+h.fragment;if((j+m).length>=g){if((j+m).length==b)return(j+m).substr(0,b);var n=g-j.length;return(j+f(m,n)).substr(0,b)}j+=m}if(h.scheme&&h.host){var o=h.scheme+"://";if((j+o).length0&&(p=j.substr(-1*Math.floor(g/2))),(j.substr(0,Math.ceil(g/2))+c+p).substr(0,b)},a}); \ No newline at end of file diff --git a/public/build/assets/js/Autolinker.min-b46556773a.js.br b/public/build/assets/js/Autolinker.min-b46556773a.js.br new file mode 100644 index 00000000..2b8b84f7 Binary files /dev/null and b/public/build/assets/js/Autolinker.min-b46556773a.js.br differ diff --git a/public/build/assets/js/Autolinker.min-b46556773a.js.gz b/public/build/assets/js/Autolinker.min-b46556773a.js.gz new file mode 100644 index 00000000..f0c2a0da Binary files /dev/null and b/public/build/assets/js/Autolinker.min-b46556773a.js.gz differ diff --git a/public/build/assets/js/alertify-269e23cb46.js b/public/build/assets/js/alertify-269e23cb46.js new file mode 100644 index 00000000..d9d6a2b2 --- /dev/null +++ b/public/build/assets/js/alertify-269e23cb46.js @@ -0,0 +1 @@ +!function(){"use strict";function t(){var t={version:"1.0.8",defaultOkLabel:"Ok",okLabel:"Ok",defaultCancelLabel:"Cancel",cancelLabel:"Cancel",defaultMaxLogItems:2,maxLogItems:2,promptValue:"",promptPlaceholder:"",closeLogOnClick:!1,closeLogOnClickDefault:!1,delay:5e3,defaultDelay:5e3,logContainerClass:"alertify-logs",logContainerDefaultClass:"alertify-logs",dialogs:{buttons:{holder:"",ok:"",cancel:""},input:"",message:"

    {{message}}

    ",log:"
    {{message}}
    "},defaultDialogs:{buttons:{holder:"",ok:"",cancel:""},input:"",message:"

    {{message}}

    ",log:"
    {{message}}
    "},build:function(t){var e=this.dialogs.buttons.ok,o="
    "+this.dialogs.message.replace("{{message}}",t.message);return"confirm"!==t.type&&"prompt"!==t.type||(e=this.dialogs.buttons.cancel+this.dialogs.buttons.ok),"prompt"===t.type&&(o+=this.dialogs.input),o=(o+this.dialogs.buttons.holder+"
    ").replace("{{buttons}}",e).replace("{{ok}}",this.okLabel).replace("{{cancel}}",this.cancelLabel)},setCloseLogOnClick:function(t){this.closeLogOnClick=!!t},close:function(t,e){this.closeLogOnClick&&t.addEventListener("click",function(t){o(t.srcElement)}),e=e&&!isNaN(+e)?+e:this.delay,0>e?o(t):e>0&&setTimeout(function(){o(t)},e)},dialog:function(t,e,o,n){return this.setup({type:e,message:t,onOkay:o,onCancel:n})},log:function(t,e,o){var n=document.querySelectorAll(".alertify-logs > div");if(n){var i=n.length-this.maxLogItems;if(i>=0)for(var a=0,l=i+1;l>a;a++)this.close(n[a],-1)}this.notify(t,e,o)},setLogPosition:function(t){this.logContainerClass="alertify-logs "+t},setupLogContainer:function(){var t=document.querySelector(".alertify-logs"),e=this.logContainerClass;return t||(t=document.createElement("div"),t.className=e,document.body.appendChild(t)),t.className!==e&&(t.className=e),t},notify:function(e,o,n){var i=this.setupLogContainer(),a=document.createElement("div");a.className=o||"default",t.logTemplateMethod?a.innerHTML=t.logTemplateMethod(e):a.innerHTML=e,"function"==typeof n&&a.addEventListener("click",n),i.appendChild(a),setTimeout(function(){a.className+=" show"},10),this.close(a,this.delay)},setup:function(t){function e(e){"function"!=typeof e&&(e=function(){}),i&&i.addEventListener("click",function(i){t.onOkay&&"function"==typeof t.onOkay&&(l?t.onOkay(l.value,i):t.onOkay(i)),e(l?{buttonClicked:"ok",inputValue:l.value,event:i}:{buttonClicked:"ok",event:i}),o(n)}),a&&a.addEventListener("click",function(i){t.onCancel&&"function"==typeof t.onCancel&&t.onCancel(i),e({buttonClicked:"cancel",event:i}),o(n)})}var n=document.createElement("div");n.className="alertify hide",n.innerHTML=this.build(t);var i=n.querySelector(".ok"),a=n.querySelector(".cancel"),l=n.querySelector("input"),s=n.querySelector("label");l&&("string"==typeof this.promptPlaceholder&&(s?s.textContent=this.promptPlaceholder:l.placeholder=this.promptPlaceholder),"string"==typeof this.promptValue&&(l.value=this.promptValue));var r;return"function"==typeof Promise?r=new Promise(e):e(),document.body.appendChild(n),setTimeout(function(){n.classList.remove("hide"),l&&t.type&&"prompt"===t.type?(l.select(),l.focus()):i&&i.focus()},100),r},okBtn:function(t){return this.okLabel=t,this},setDelay:function(t){var e=parseInt(t||0,10);return this.delay=isNaN(e)?this.defultDelay:t,this},cancelBtn:function(t){return this.cancelLabel=t,this},setMaxLogItems:function(t){this.maxLogItems=parseInt(t||this.defaultMaxLogItems)},theme:function(t){switch(t.toLowerCase()){case"bootstrap":this.dialogs.buttons.ok="",this.dialogs.buttons.cancel="",this.dialogs.input="";break;case"purecss":this.dialogs.buttons.ok="",this.dialogs.buttons.cancel="";break;case"mdl":case"material-design-light":this.dialogs.buttons.ok="",this.dialogs.buttons.cancel="",this.dialogs.input="
    ";break;case"angular-material":this.dialogs.buttons.ok="",this.dialogs.buttons.cancel="",this.dialogs.input="
    ";break;case"default":default:this.dialogs.buttons.ok=this.defaultDialogs.buttons.ok,this.dialogs.buttons.cancel=this.defaultDialogs.buttons.cancel,this.dialogs.input=this.defaultDialogs.input}},reset:function(){this.theme("default"),this.okBtn(this.defaultOkLabel),this.cancelBtn(this.defaultCancelLabel),this.setMaxLogItems(),this.promptValue="",this.promptPlaceholder="",this.delay=this.defaultDelay,this.setCloseLogOnClick(this.closeLogOnClickDefault),this.setLogPosition("bottom left"),this.logTemplateMethod=null},injectCSS:function(){if(!document.querySelector("#alertifyCSS")){var t=document.getElementsByTagName("head")[0],e=document.createElement("style");e.type="text/css",e.id="alertifyCSS",e.innerHTML=".alertify-logs>*{padding:12px 24px;color:#fff;box-shadow:0 2px 5px 0 rgba(0,0,0,.2);border-radius:1px}.alertify-logs>*,.alertify-logs>.default{background:rgba(0,0,0,.8)}.alertify-logs>.error{background:rgba(244,67,54,.8)}.alertify-logs>.success{background:rgba(76,175,80,.9)}.alertify{position:fixed;background-color:rgba(0,0,0,.3);left:0;right:0;top:0;bottom:0;width:100%;height:100%;z-index:2}.alertify.hide{opacity:0;pointer-events:none}.alertify,.alertify.show{box-sizing:border-box;transition:all .33s cubic-bezier(.25,.8,.25,1)}.alertify,.alertify *{box-sizing:border-box}.alertify .dialog{padding:12px}.alertify .alert,.alertify .dialog{width:100%;margin:0 auto;position:relative;top:50%;-webkit-transform:translateY(-50%);transform:translateY(-50%)}.alertify .alert>*,.alertify .dialog>*{width:400px;max-width:95%;margin:0 auto;text-align:center;padding:12px;background:#fff;box-shadow:0 2px 4px -1px rgba(0,0,0,.14),0 4px 5px 0 rgba(0,0,0,.098),0 1px 10px 0 rgba(0,0,0,.084)}.alertify .alert .msg,.alertify .dialog .msg{padding:12px;margin-bottom:12px;margin:0;text-align:left}.alertify .alert input:not(.form-control),.alertify .dialog input:not(.form-control){margin-bottom:15px;width:100%;font-size:100%;padding:12px}.alertify .alert input:not(.form-control):focus,.alertify .dialog input:not(.form-control):focus{outline-offset:-2px}.alertify .alert nav,.alertify .dialog nav{text-align:right}.alertify .alert nav button:not(.btn):not(.pure-button):not(.md-button):not(.mdl-button),.alertify .dialog nav button:not(.btn):not(.pure-button):not(.md-button):not(.mdl-button){background:transparent;box-sizing:border-box;color:rgba(0,0,0,.87);position:relative;outline:0;border:0;display:inline-block;-webkit-align-items:center;-ms-flex-align:center;-ms-grid-row-align:center;align-items:center;padding:0 6px;margin:6px 8px;line-height:36px;min-height:36px;white-space:nowrap;min-width:88px;text-align:center;text-transform:uppercase;font-size:14px;text-decoration:none;cursor:pointer;border:1px solid transparent;border-radius:2px}.alertify .alert nav button:not(.btn):not(.pure-button):not(.md-button):not(.mdl-button):active,.alertify .alert nav button:not(.btn):not(.pure-button):not(.md-button):not(.mdl-button):hover,.alertify .dialog nav button:not(.btn):not(.pure-button):not(.md-button):not(.mdl-button):active,.alertify .dialog nav button:not(.btn):not(.pure-button):not(.md-button):not(.mdl-button):hover{background-color:rgba(0,0,0,.05)}.alertify .alert nav button:not(.btn):not(.pure-button):not(.md-button):not(.mdl-button):focus,.alertify .dialog nav button:not(.btn):not(.pure-button):not(.md-button):not(.mdl-button):focus{border:1px solid rgba(0,0,0,.1)}.alertify .alert nav button.btn,.alertify .dialog nav button.btn{margin:6px 4px}.alertify-logs{position:fixed;z-index:1}.alertify-logs.bottom,.alertify-logs:not(.top){bottom:16px}.alertify-logs.left,.alertify-logs:not(.right){left:16px}.alertify-logs.left>*,.alertify-logs:not(.right)>*{float:left;-webkit-transform:translateZ(0);transform:translateZ(0);height:auto}.alertify-logs.left>.show,.alertify-logs:not(.right)>.show{left:0}.alertify-logs.left>*,.alertify-logs.left>.hide,.alertify-logs:not(.right)>*,.alertify-logs:not(.right)>.hide{left:-110%}.alertify-logs.right{right:16px}.alertify-logs.right>*{float:right;-webkit-transform:translateZ(0);transform:translateZ(0)}.alertify-logs.right>.show{right:0;opacity:1}.alertify-logs.right>*,.alertify-logs.right>.hide{right:-110%;opacity:0}.alertify-logs.top{top:0}.alertify-logs>*{box-sizing:border-box;transition:all .4s cubic-bezier(.25,.8,.25,1);position:relative;clear:both;-webkit-backface-visibility:hidden;backface-visibility:hidden;-webkit-perspective:1000;perspective:1000;max-height:0;margin:0;padding:0;overflow:hidden;opacity:0;pointer-events:none}.alertify-logs>.show{margin-top:12px;opacity:1;max-height:1000px;padding:12px;pointer-events:auto}",t.insertBefore(e,t.firstChild)}},removeCSS:function(){var t=document.querySelector("#alertifyCSS");t&&t.parentNode&&t.parentNode.removeChild(t)}};return t.injectCSS(),{_$$alertify:t,reset:function(){return t.reset(),this},alert:function(e,o,n){return t.dialog(e,"alert",o,n)||this},confirm:function(e,o,n){return t.dialog(e,"confirm",o,n)||this},prompt:function(e,o,n){return t.dialog(e,"prompt",o,n)||this},log:function(e,o){return t.log(e,"default",o),this},theme:function(e){return t.theme(e),this},success:function(e,o){return t.log(e,"success",o),this},error:function(e,o){return t.log(e,"error",o),this},cancelBtn:function(e){return t.cancelBtn(e),this},okBtn:function(e){return t.okBtn(e),this},delay:function(e){return t.setDelay(e),this},placeholder:function(e){return t.promptPlaceholder=e,this},defaultValue:function(e){return t.promptValue=e,this},maxLogItems:function(e){return t.setMaxLogItems(e),this},closeLogOnClick:function(e){return t.setCloseLogOnClick(!!e),this},logPosition:function(e){return t.setLogPosition(e||""),this},setLogTemplate:function(e){return t.logTemplateMethod=e,this},clearLogs:function(){return t.setupLogContainer().innerHTML="",this},version:t.version}}var e=500,o=function(t){if(t){var o=function(){t&&t.parentNode&&t.parentNode.removeChild(t)};t.classList.remove("show"),t.classList.add("hide"),t.addEventListener("transitionend",o),setTimeout(o,e)}};if("undefined"!=typeof module&&module&&module.exports){module.exports=function(){return new t};var n=new t;for(var i in n)module.exports[i]=n[i]}else"function"==typeof define&&define.amd?define(function(){return new t}):window.alertify=new t}(); \ No newline at end of file diff --git a/public/build/assets/js/alertify-269e23cb46.js.br b/public/build/assets/js/alertify-269e23cb46.js.br new file mode 100644 index 00000000..69f96a39 Binary files /dev/null and b/public/build/assets/js/alertify-269e23cb46.js.br differ diff --git a/public/build/assets/js/alertify-269e23cb46.js.gz b/public/build/assets/js/alertify-269e23cb46.js.gz new file mode 100644 index 00000000..b3205c6b Binary files /dev/null and b/public/build/assets/js/alertify-269e23cb46.js.gz differ diff --git a/public/build/assets/js/fetch-5e9040330a.js b/public/build/assets/js/fetch-5e9040330a.js new file mode 100644 index 00000000..fac11e42 --- /dev/null +++ b/public/build/assets/js/fetch-5e9040330a.js @@ -0,0 +1,389 @@ +(function(self) { + 'use strict'; + + if (self.fetch) { + return + } + + function normalizeName(name) { + if (typeof name !== 'string') { + name = String(name) + } + if (/[^a-z0-9\-#$%&'*+.\^_`|~]/i.test(name)) { + throw new TypeError('Invalid character in header field name') + } + return name.toLowerCase() + } + + function normalizeValue(value) { + if (typeof value !== 'string') { + value = String(value) + } + return value + } + + function Headers(headers) { + this.map = {} + + if (headers instanceof Headers) { + headers.forEach(function(value, name) { + this.append(name, value) + }, this) + + } else if (headers) { + Object.getOwnPropertyNames(headers).forEach(function(name) { + this.append(name, headers[name]) + }, this) + } + } + + Headers.prototype.append = function(name, value) { + name = normalizeName(name) + value = normalizeValue(value) + var list = this.map[name] + if (!list) { + list = [] + this.map[name] = list + } + list.push(value) + } + + Headers.prototype['delete'] = function(name) { + delete this.map[normalizeName(name)] + } + + Headers.prototype.get = function(name) { + var values = this.map[normalizeName(name)] + return values ? values[0] : null + } + + Headers.prototype.getAll = function(name) { + return this.map[normalizeName(name)] || [] + } + + Headers.prototype.has = function(name) { + return this.map.hasOwnProperty(normalizeName(name)) + } + + Headers.prototype.set = function(name, value) { + this.map[normalizeName(name)] = [normalizeValue(value)] + } + + Headers.prototype.forEach = function(callback, thisArg) { + Object.getOwnPropertyNames(this.map).forEach(function(name) { + this.map[name].forEach(function(value) { + callback.call(thisArg, value, name, this) + }, this) + }, this) + } + + function consumed(body) { + if (body.bodyUsed) { + return Promise.reject(new TypeError('Already read')) + } + body.bodyUsed = true + } + + function fileReaderReady(reader) { + return new Promise(function(resolve, reject) { + reader.onload = function() { + resolve(reader.result) + } + reader.onerror = function() { + reject(reader.error) + } + }) + } + + function readBlobAsArrayBuffer(blob) { + var reader = new FileReader() + reader.readAsArrayBuffer(blob) + return fileReaderReady(reader) + } + + function readBlobAsText(blob) { + var reader = new FileReader() + reader.readAsText(blob) + return fileReaderReady(reader) + } + + var support = { + blob: 'FileReader' in self && 'Blob' in self && (function() { + try { + new Blob(); + return true + } catch(e) { + return false + } + })(), + formData: 'FormData' in self, + arrayBuffer: 'ArrayBuffer' in self + } + + function Body() { + this.bodyUsed = false + + + this._initBody = function(body) { + this._bodyInit = body + if (typeof body === 'string') { + this._bodyText = body + } else if (support.blob && Blob.prototype.isPrototypeOf(body)) { + this._bodyBlob = body + } else if (support.formData && FormData.prototype.isPrototypeOf(body)) { + this._bodyFormData = body + } else if (!body) { + this._bodyText = '' + } else if (support.arrayBuffer && ArrayBuffer.prototype.isPrototypeOf(body)) { + // Only support ArrayBuffers for POST method. + // Receiving ArrayBuffers happens via Blobs, instead. + } else { + throw new Error('unsupported BodyInit type') + } + + if (!this.headers.get('content-type')) { + if (typeof body === 'string') { + this.headers.set('content-type', 'text/plain;charset=UTF-8') + } else if (this._bodyBlob && this._bodyBlob.type) { + this.headers.set('content-type', this._bodyBlob.type) + } + } + } + + if (support.blob) { + this.blob = function() { + var rejected = consumed(this) + if (rejected) { + return rejected + } + + if (this._bodyBlob) { + return Promise.resolve(this._bodyBlob) + } else if (this._bodyFormData) { + throw new Error('could not read FormData body as blob') + } else { + return Promise.resolve(new Blob([this._bodyText])) + } + } + + this.arrayBuffer = function() { + return this.blob().then(readBlobAsArrayBuffer) + } + + this.text = function() { + var rejected = consumed(this) + if (rejected) { + return rejected + } + + if (this._bodyBlob) { + return readBlobAsText(this._bodyBlob) + } else if (this._bodyFormData) { + throw new Error('could not read FormData body as text') + } else { + return Promise.resolve(this._bodyText) + } + } + } else { + this.text = function() { + var rejected = consumed(this) + return rejected ? rejected : Promise.resolve(this._bodyText) + } + } + + if (support.formData) { + this.formData = function() { + return this.text().then(decode) + } + } + + this.json = function() { + return this.text().then(JSON.parse) + } + + return this + } + + // HTTP methods whose capitalization should be normalized + var methods = ['DELETE', 'GET', 'HEAD', 'OPTIONS', 'POST', 'PUT'] + + function normalizeMethod(method) { + var upcased = method.toUpperCase() + return (methods.indexOf(upcased) > -1) ? upcased : method + } + + function Request(input, options) { + options = options || {} + var body = options.body + if (Request.prototype.isPrototypeOf(input)) { + if (input.bodyUsed) { + throw new TypeError('Already read') + } + this.url = input.url + this.credentials = input.credentials + if (!options.headers) { + this.headers = new Headers(input.headers) + } + this.method = input.method + this.mode = input.mode + if (!body) { + body = input._bodyInit + input.bodyUsed = true + } + } else { + this.url = input + } + + this.credentials = options.credentials || this.credentials || 'omit' + if (options.headers || !this.headers) { + this.headers = new Headers(options.headers) + } + this.method = normalizeMethod(options.method || this.method || 'GET') + this.mode = options.mode || this.mode || null + this.referrer = null + + if ((this.method === 'GET' || this.method === 'HEAD') && body) { + throw new TypeError('Body not allowed for GET or HEAD requests') + } + this._initBody(body) + } + + Request.prototype.clone = function() { + return new Request(this) + } + + function decode(body) { + var form = new FormData() + body.trim().split('&').forEach(function(bytes) { + if (bytes) { + var split = bytes.split('=') + var name = split.shift().replace(/\+/g, ' ') + var value = split.join('=').replace(/\+/g, ' ') + form.append(decodeURIComponent(name), decodeURIComponent(value)) + } + }) + return form + } + + function headers(xhr) { + var head = new Headers() + var pairs = xhr.getAllResponseHeaders().trim().split('\n') + pairs.forEach(function(header) { + var split = header.trim().split(':') + var key = split.shift().trim() + var value = split.join(':').trim() + head.append(key, value) + }) + return head + } + + Body.call(Request.prototype) + + function Response(bodyInit, options) { + if (!options) { + options = {} + } + + this.type = 'default' + this.status = options.status + this.ok = this.status >= 200 && this.status < 300 + this.statusText = options.statusText + this.headers = options.headers instanceof Headers ? options.headers : new Headers(options.headers) + this.url = options.url || '' + this._initBody(bodyInit) + } + + Body.call(Response.prototype) + + Response.prototype.clone = function() { + return new Response(this._bodyInit, { + status: this.status, + statusText: this.statusText, + headers: new Headers(this.headers), + url: this.url + }) + } + + Response.error = function() { + var response = new Response(null, {status: 0, statusText: ''}) + response.type = 'error' + return response + } + + var redirectStatuses = [301, 302, 303, 307, 308] + + Response.redirect = function(url, status) { + if (redirectStatuses.indexOf(status) === -1) { + throw new RangeError('Invalid status code') + } + + return new Response(null, {status: status, headers: {location: url}}) + } + + self.Headers = Headers; + self.Request = Request; + self.Response = Response; + + self.fetch = function(input, init) { + return new Promise(function(resolve, reject) { + var request + if (Request.prototype.isPrototypeOf(input) && !init) { + request = input + } else { + request = new Request(input, init) + } + + var xhr = new XMLHttpRequest() + + function responseURL() { + if ('responseURL' in xhr) { + return xhr.responseURL + } + + // Avoid security warnings on getResponseHeader when not allowed by CORS + if (/^X-Request-URL:/m.test(xhr.getAllResponseHeaders())) { + return xhr.getResponseHeader('X-Request-URL') + } + + return; + } + + xhr.onload = function() { + var status = (xhr.status === 1223) ? 204 : xhr.status + if (status < 100 || status > 599) { + reject(new TypeError('Network request failed')) + return + } + var options = { + status: status, + statusText: xhr.statusText, + headers: headers(xhr), + url: responseURL() + } + var body = 'response' in xhr ? xhr.response : xhr.responseText; + resolve(new Response(body, options)) + } + + xhr.onerror = function() { + reject(new TypeError('Network request failed')) + } + + xhr.open(request.method, request.url, true) + + if (request.credentials === 'include') { + xhr.withCredentials = true + } + + if ('responseType' in xhr && support.blob) { + xhr.responseType = 'blob' + } + + request.headers.forEach(function(value, name) { + xhr.setRequestHeader(name, value) + }) + + xhr.send(typeof request._bodyInit === 'undefined' ? null : request._bodyInit) + }) + } + self.fetch.polyfill = true +})(typeof self !== 'undefined' ? self : this); diff --git a/public/build/assets/js/fetch-5e9040330a.js.br b/public/build/assets/js/fetch-5e9040330a.js.br new file mode 100644 index 00000000..682a42a0 Binary files /dev/null and b/public/build/assets/js/fetch-5e9040330a.js.br differ diff --git a/public/build/assets/js/fetch-5e9040330a.js.gz b/public/build/assets/js/fetch-5e9040330a.js.gz new file mode 100644 index 00000000..af7036e6 Binary files /dev/null and b/public/build/assets/js/fetch-5e9040330a.js.gz differ diff --git a/public/build/assets/js/form-save-7849d1a5f3.js b/public/build/assets/js/form-save-7849d1a5f3.js new file mode 100644 index 00000000..151d1219 --- /dev/null +++ b/public/build/assets/js/form-save-7849d1a5f3.js @@ -0,0 +1,69 @@ +var feature = { + addEventListener : !!window.addEventListener, + querySelectorAll : !!document.querySelectorAll, +}; +if(feature.addEventListener && feature.querySelectorAll) { + this.init(); +} +function init() { + var keys = getKeys(); + for(var i = 0; i < keys.length; i++) { + if(store.get(keys[i])) { + var formId = keys[i].split("~")[1]; + document.getElementById(formId).value = store.get(keys[i]); + } + } +} +var timerId = window.setInterval(function() { + var saved = false; + var inputs = document.querySelectorAll('input[type=text], textarea'); + for(var i = 0; i < inputs.length; i++) { + var key = getFormElement(inputs[i]).id + '~' + inputs[i].id; + if(store.get(key) !== inputs[i].value && inputs[i].value !== "") { + store.set(key, inputs[i].value); + saved = true; + } + } + if(saved === true) { + alertify.logPosition('top right'); + alertify.success('Auto saved text'); + } +}, 5000); +var forms = document.querySelectorAll('form'); +for(var f = 0; f < forms.length; f++) { + var form = forms[f]; + form.addEventListener('submit', function() { + window.clearInterval(timerId); + var formId = form.id; + var storedKeys = store.keys(); + for(var i = 0; i < storedKeys.length; i++) { + if(storedKeys[i].indexOf(formId) > -1) { + store.remove(storedKeys[i]); + } + } + }); +} +function getKeys() { + var keys = []; + var formFields = document.querySelectorAll('input[type=text], textarea'); + for(var f = 0; f < formFields.length; f++) { + var parent = getFormElement(formFields[f]); + if(parent !== false) { + var key = parent.id + '~' + formFields[f].id; + keys.push(key); + } + } + return keys; +} +function getFormElement(elem) { + if(elem.nodeName.toLowerCase() !== 'body') { + var parent = elem.parentNode; + if(parent.nodeName.toLowerCase() === 'form') { + return parent; + } else { + return getFormElement(parent); + } + } else { + return false; + } +} diff --git a/public/build/assets/js/form-save-7849d1a5f3.js.br b/public/build/assets/js/form-save-7849d1a5f3.js.br new file mode 100644 index 00000000..6fc60195 Binary files /dev/null and b/public/build/assets/js/form-save-7849d1a5f3.js.br differ diff --git a/public/build/assets/js/form-save-7849d1a5f3.js.gz b/public/build/assets/js/form-save-7849d1a5f3.js.gz new file mode 100644 index 00000000..ff873cbb Binary files /dev/null and b/public/build/assets/js/form-save-7849d1a5f3.js.gz differ diff --git a/public/build/assets/js/links-ea4c99f585.js b/public/build/assets/js/links-ea4c99f585.js new file mode 100644 index 00000000..b683edd5 --- /dev/null +++ b/public/build/assets/js/links-ea4c99f585.js @@ -0,0 +1,25 @@ +//the autlinker object +var autolinker = new Autolinker(); + +//the youtube regex +var ytidregex = /watch\?v=([A-Za-z0-9\-_]+)/; + +//grab the notes and loop through them +var notes = document.querySelectorAll('.e-content'); +for(var i = 0; i < notes.length; i++) { + //get Youtube ID + var ytid = notes[i].textContent.match(ytidregex); + if(ytid !== null) { + var id = ytid[1]; + var iframe = document.createElement('iframe'); + iframe.classList.add('youtube'); + iframe.setAttribute('src', '//www.youtube.com/embed/' + id); + iframe.setAttribute('frameborder', 0); + iframe.setAttribute('allowfullscreen', 'true'); + notes[i].appendChild(iframe); + } + //now linkify everything + var orig = notes[i].innerHTML; + var linked = autolinker.link(orig); + notes[i].innerHTML = linked; +} \ No newline at end of file diff --git a/public/build/assets/js/links-ea4c99f585.js.br b/public/build/assets/js/links-ea4c99f585.js.br new file mode 100644 index 00000000..570224c5 Binary files /dev/null and b/public/build/assets/js/links-ea4c99f585.js.br differ diff --git a/public/build/assets/js/links-ea4c99f585.js.gz b/public/build/assets/js/links-ea4c99f585.js.gz new file mode 100644 index 00000000..bb58b733 Binary files /dev/null and b/public/build/assets/js/links-ea4c99f585.js.gz differ diff --git a/public/build/assets/js/maps-ffa37774ae.js b/public/build/assets/js/maps-ffa37774ae.js new file mode 100644 index 00000000..4901c5da --- /dev/null +++ b/public/build/assets/js/maps-ffa37774ae.js @@ -0,0 +1,15 @@ +//This code runs on page load and looks for
    , then adds map +var mapDivs = document.querySelectorAll('.map'); +for(var i = 0; i < mapDivs.length; i++) { + var mapDiv = mapDivs[i]; + var latitude = mapDiv.dataset.latitude; + var longitude = mapDiv.dataset.longitude; + L.mapbox.accessToken = 'pk.eyJ1Ijoiam9ubnliYXJuZXMiLCJhIjoiVlpndW1EYyJ9.aP9fxAqLKh7lj0LpFh5k1w'; + var map = L.mapbox.map(mapDiv, 'jonnybarnes.gnoihnim') + .setView([latitude, longitude], 15) + .addLayer(L.mapbox.tileLayer('jonnybarnes.gnoihnim', { + detectRetina: true, + })); + var marker = L.marker([latitude, longitude]).addTo(map); + map.scrollWheelZoom.disable(); +} diff --git a/public/build/assets/js/maps-ffa37774ae.js.br b/public/build/assets/js/maps-ffa37774ae.js.br new file mode 100644 index 00000000..ef0a316d Binary files /dev/null and b/public/build/assets/js/maps-ffa37774ae.js.br differ diff --git a/public/build/assets/js/maps-ffa37774ae.js.gz b/public/build/assets/js/maps-ffa37774ae.js.gz new file mode 100644 index 00000000..f100f82c Binary files /dev/null and b/public/build/assets/js/maps-ffa37774ae.js.gz differ diff --git a/public/build/assets/js/marked.min-c2a88705e2.js b/public/build/assets/js/marked.min-c2a88705e2.js new file mode 100644 index 00000000..555c1dc1 --- /dev/null +++ b/public/build/assets/js/marked.min-c2a88705e2.js @@ -0,0 +1,6 @@ +/** + * marked - a markdown parser + * Copyright (c) 2011-2014, Christopher Jeffrey. (MIT Licensed) + * https://github.com/chjj/marked + */ +(function(){var block={newline:/^\n+/,code:/^( {4}[^\n]+\n*)+/,fences:noop,hr:/^( *[-*_]){3,} *(?:\n+|$)/,heading:/^ *(#{1,6}) *([^\n]+?) *#* *(?:\n+|$)/,nptable:noop,lheading:/^([^\n]+)\n *(=|-){2,} *(?:\n+|$)/,blockquote:/^( *>[^\n]+(\n(?!def)[^\n]+)*\n*)+/,list:/^( *)(bull) [\s\S]+?(?:hr|def|\n{2,}(?! )(?!\1bull )\n*|\s*$)/,html:/^ *(?:comment *(?:\n|\s*$)|closed *(?:\n{2,}|\s*$)|closing *(?:\n{2,}|\s*$))/,def:/^ *\[([^\]]+)\]: *]+)>?(?: +["(]([^\n]+)[")])? *(?:\n+|$)/,table:noop,paragraph:/^((?:[^\n]+\n?(?!hr|heading|lheading|blockquote|tag|def))+)\n*/,text:/^[^\n]+/};block.bullet=/(?:[*+-]|\d+\.)/;block.item=/^( *)(bull) [^\n]*(?:\n(?!\1bull )[^\n]*)*/;block.item=replace(block.item,"gm")(/bull/g,block.bullet)();block.list=replace(block.list)(/bull/g,block.bullet)("hr","\\n+(?=\\1?(?:[-*_] *){3,}(?:\\n+|$))")("def","\\n+(?="+block.def.source+")")();block.blockquote=replace(block.blockquote)("def",block.def)();block._tag="(?!(?:"+"a|em|strong|small|s|cite|q|dfn|abbr|data|time|code"+"|var|samp|kbd|sub|sup|i|b|u|mark|ruby|rt|rp|bdi|bdo"+"|span|br|wbr|ins|del|img)\\b)\\w+(?!:/|[^\\w\\s@]*@)\\b";block.html=replace(block.html)("comment",//)("closed",/<(tag)[\s\S]+?<\/\1>/)("closing",/])*?>/)(/tag/g,block._tag)();block.paragraph=replace(block.paragraph)("hr",block.hr)("heading",block.heading)("lheading",block.lheading)("blockquote",block.blockquote)("tag","<"+block._tag)("def",block.def)();block.normal=merge({},block);block.gfm=merge({},block.normal,{fences:/^ *(`{3,}|~{3,})[ \.]*(\S+)? *\n([\s\S]*?)\s*\1 *(?:\n+|$)/,paragraph:/^/,heading:/^ *(#{1,6}) +([^\n]+?) *#* *(?:\n+|$)/});block.gfm.paragraph=replace(block.paragraph)("(?!","(?!"+block.gfm.fences.source.replace("\\1","\\2")+"|"+block.list.source.replace("\\1","\\3")+"|")();block.tables=merge({},block.gfm,{nptable:/^ *(\S.*\|.*)\n *([-:]+ *\|[-| :]*)\n((?:.*\|.*(?:\n|$))*)\n*/,table:/^ *\|(.+)\n *\|( *[-:]+[-| :]*)\n((?: *\|.*(?:\n|$))*)\n*/});function Lexer(options){this.tokens=[];this.tokens.links={};this.options=options||marked.defaults;this.rules=block.normal;if(this.options.gfm){if(this.options.tables){this.rules=block.tables}else{this.rules=block.gfm}}}Lexer.rules=block;Lexer.lex=function(src,options){var lexer=new Lexer(options);return lexer.lex(src)};Lexer.prototype.lex=function(src){src=src.replace(/\r\n|\r/g,"\n").replace(/\t/g," ").replace(/\u00a0/g," ").replace(/\u2424/g,"\n");return this.token(src,true)};Lexer.prototype.token=function(src,top,bq){var src=src.replace(/^ +$/gm,""),next,loose,cap,bull,b,item,space,i,l;while(src){if(cap=this.rules.newline.exec(src)){src=src.substring(cap[0].length);if(cap[0].length>1){this.tokens.push({type:"space"})}}if(cap=this.rules.code.exec(src)){src=src.substring(cap[0].length);cap=cap[0].replace(/^ {4}/gm,"");this.tokens.push({type:"code",text:!this.options.pedantic?cap.replace(/\n+$/,""):cap});continue}if(cap=this.rules.fences.exec(src)){src=src.substring(cap[0].length);this.tokens.push({type:"code",lang:cap[2],text:cap[3]||""});continue}if(cap=this.rules.heading.exec(src)){src=src.substring(cap[0].length);this.tokens.push({type:"heading",depth:cap[1].length,text:cap[2]});continue}if(top&&(cap=this.rules.nptable.exec(src))){src=src.substring(cap[0].length);item={type:"table",header:cap[1].replace(/^ *| *\| *$/g,"").split(/ *\| */),align:cap[2].replace(/^ *|\| *$/g,"").split(/ *\| */),cells:cap[3].replace(/\n$/,"").split("\n")};for(i=0;i ?/gm,"");this.token(cap,top,true);this.tokens.push({type:"blockquote_end"});continue}if(cap=this.rules.list.exec(src)){src=src.substring(cap[0].length);bull=cap[2];this.tokens.push({type:"list_start",ordered:bull.length>1});cap=cap[0].match(this.rules.item);next=false;l=cap.length;i=0;for(;i1&&b.length>1)){src=cap.slice(i+1).join("\n")+src;i=l-1}}loose=next||/\n\n(?!\s*$)/.test(item);if(i!==l-1){next=item.charAt(item.length-1)==="\n";if(!loose)loose=next}this.tokens.push({type:loose?"loose_item_start":"list_item_start"});this.token(item,false,bq);this.tokens.push({type:"list_item_end"})}this.tokens.push({type:"list_end"});continue}if(cap=this.rules.html.exec(src)){src=src.substring(cap[0].length);this.tokens.push({type:this.options.sanitize?"paragraph":"html",pre:!this.options.sanitizer&&(cap[1]==="pre"||cap[1]==="script"||cap[1]==="style"),text:cap[0]});continue}if(!bq&&top&&(cap=this.rules.def.exec(src))){src=src.substring(cap[0].length);this.tokens.links[cap[1].toLowerCase()]={href:cap[2],title:cap[3]};continue}if(top&&(cap=this.rules.table.exec(src))){src=src.substring(cap[0].length);item={type:"table",header:cap[1].replace(/^ *| *\| *$/g,"").split(/ *\| */),align:cap[2].replace(/^ *|\| *$/g,"").split(/ *\| */),cells:cap[3].replace(/(?: *\| *)?\n$/,"").split("\n")};for(i=0;i])/,autolink:/^<([^ >]+(@|:\/)[^ >]+)>/,url:noop,tag:/^|^<\/?\w+(?:"[^"]*"|'[^']*'|[^'">])*?>/,link:/^!?\[(inside)\]\(href\)/,reflink:/^!?\[(inside)\]\s*\[([^\]]*)\]/,nolink:/^!?\[((?:\[[^\]]*\]|[^\[\]])*)\]/,strong:/^__([\s\S]+?)__(?!_)|^\*\*([\s\S]+?)\*\*(?!\*)/,em:/^\b_((?:[^_]|__)+?)_\b|^\*((?:\*\*|[\s\S])+?)\*(?!\*)/,code:/^(`+)\s*([\s\S]*?[^`])\s*\1(?!`)/,br:/^ {2,}\n(?!\s*$)/,del:noop,text:/^[\s\S]+?(?=[\\?(?:\s+['"]([\s\S]*?)['"])?\s*/;inline.link=replace(inline.link)("inside",inline._inside)("href",inline._href)();inline.reflink=replace(inline.reflink)("inside",inline._inside)();inline.normal=merge({},inline);inline.pedantic=merge({},inline.normal,{strong:/^__(?=\S)([\s\S]*?\S)__(?!_)|^\*\*(?=\S)([\s\S]*?\S)\*\*(?!\*)/,em:/^_(?=\S)([\s\S]*?\S)_(?!_)|^\*(?=\S)([\s\S]*?\S)\*(?!\*)/});inline.gfm=merge({},inline.normal,{escape:replace(inline.escape)("])","~|])")(),url:/^(https?:\/\/[^\s<]+[^<.,:;"')\]\s])/,del:/^~~(?=\S)([\s\S]*?\S)~~/,text:replace(inline.text)("]|","~]|")("|","|https?://|")()});inline.breaks=merge({},inline.gfm,{br:replace(inline.br)("{2,}","*")(),text:replace(inline.gfm.text)("{2,}","*")()});function InlineLexer(links,options){this.options=options||marked.defaults;this.links=links;this.rules=inline.normal;this.renderer=this.options.renderer||new Renderer;this.renderer.options=this.options;if(!this.links){throw new Error("Tokens array requires a `links` property.")}if(this.options.gfm){if(this.options.breaks){this.rules=inline.breaks}else{this.rules=inline.gfm}}else if(this.options.pedantic){this.rules=inline.pedantic}}InlineLexer.rules=inline;InlineLexer.output=function(src,links,options){var inline=new InlineLexer(links,options);return inline.output(src)};InlineLexer.prototype.output=function(src){var out="",link,text,href,cap;while(src){if(cap=this.rules.escape.exec(src)){src=src.substring(cap[0].length);out+=cap[1];continue}if(cap=this.rules.autolink.exec(src)){src=src.substring(cap[0].length);if(cap[2]==="@"){text=cap[1].charAt(6)===":"?this.mangle(cap[1].substring(7)):this.mangle(cap[1]);href=this.mangle("mailto:")+text}else{text=escape(cap[1]);href=text}out+=this.renderer.link(href,null,text);continue}if(!this.inLink&&(cap=this.rules.url.exec(src))){src=src.substring(cap[0].length);text=escape(cap[1]);href=text;out+=this.renderer.link(href,null,text);continue}if(cap=this.rules.tag.exec(src)){if(!this.inLink&&/^/i.test(cap[0])){this.inLink=false}src=src.substring(cap[0].length);out+=this.options.sanitize?this.options.sanitizer?this.options.sanitizer(cap[0]):escape(cap[0]):cap[0];continue}if(cap=this.rules.link.exec(src)){src=src.substring(cap[0].length);this.inLink=true;out+=this.outputLink(cap,{href:cap[2],title:cap[3]});this.inLink=false;continue}if((cap=this.rules.reflink.exec(src))||(cap=this.rules.nolink.exec(src))){src=src.substring(cap[0].length);link=(cap[2]||cap[1]).replace(/\s+/g," ");link=this.links[link.toLowerCase()];if(!link||!link.href){out+=cap[0].charAt(0);src=cap[0].substring(1)+src;continue}this.inLink=true;out+=this.outputLink(cap,link);this.inLink=false;continue}if(cap=this.rules.strong.exec(src)){src=src.substring(cap[0].length);out+=this.renderer.strong(this.output(cap[2]||cap[1]));continue}if(cap=this.rules.em.exec(src)){src=src.substring(cap[0].length);out+=this.renderer.em(this.output(cap[2]||cap[1]));continue}if(cap=this.rules.code.exec(src)){src=src.substring(cap[0].length);out+=this.renderer.codespan(escape(cap[2],true));continue}if(cap=this.rules.br.exec(src)){src=src.substring(cap[0].length);out+=this.renderer.br();continue}if(cap=this.rules.del.exec(src)){src=src.substring(cap[0].length);out+=this.renderer.del(this.output(cap[1]));continue}if(cap=this.rules.text.exec(src)){src=src.substring(cap[0].length);out+=this.renderer.text(escape(this.smartypants(cap[0])));continue}if(src){throw new Error("Infinite loop on byte: "+src.charCodeAt(0))}}return out};InlineLexer.prototype.outputLink=function(cap,link){var href=escape(link.href),title=link.title?escape(link.title):null;return cap[0].charAt(0)!=="!"?this.renderer.link(href,title,this.output(cap[1])):this.renderer.image(href,title,escape(cap[1]))};InlineLexer.prototype.smartypants=function(text){if(!this.options.smartypants)return text;return text.replace(/---/g,"—").replace(/--/g,"–").replace(/(^|[-\u2014/(\[{"\s])'/g,"$1‘").replace(/'/g,"’").replace(/(^|[-\u2014/(\[{\u2018\s])"/g,"$1“").replace(/"/g,"”").replace(/\.{3}/g,"…")};InlineLexer.prototype.mangle=function(text){if(!this.options.mangle)return text;var out="",l=text.length,i=0,ch;for(;i.5){ch="x"+ch.toString(16)}out+="&#"+ch+";"}return out};function Renderer(options){this.options=options||{}}Renderer.prototype.code=function(code,lang,escaped){if(this.options.highlight){var out=this.options.highlight(code,lang);if(out!=null&&out!==code){escaped=true;code=out}}if(!lang){return"
    "+(escaped?code:escape(code,true))+"\n
    "}return'
    '+(escaped?code:escape(code,true))+"\n
    \n"};Renderer.prototype.blockquote=function(quote){return"
    \n"+quote+"
    \n"};Renderer.prototype.html=function(html){return html};Renderer.prototype.heading=function(text,level,raw){return"'+text+"\n"};Renderer.prototype.hr=function(){return this.options.xhtml?"
    \n":"
    \n"};Renderer.prototype.list=function(body,ordered){var type=ordered?"ol":"ul";return"<"+type+">\n"+body+"\n"};Renderer.prototype.listitem=function(text){return"
  • "+text+"
  • \n"};Renderer.prototype.paragraph=function(text){return"

    "+text+"

    \n"};Renderer.prototype.table=function(header,body){return"\n"+"\n"+header+"\n"+"\n"+body+"\n"+"
    \n"};Renderer.prototype.tablerow=function(content){return"\n"+content+"\n"};Renderer.prototype.tablecell=function(content,flags){var type=flags.header?"th":"td";var tag=flags.align?"<"+type+' style="text-align:'+flags.align+'">':"<"+type+">";return tag+content+"\n"};Renderer.prototype.strong=function(text){return""+text+""};Renderer.prototype.em=function(text){return""+text+""};Renderer.prototype.codespan=function(text){return""+text+""};Renderer.prototype.br=function(){return this.options.xhtml?"
    ":"
    "};Renderer.prototype.del=function(text){return""+text+""};Renderer.prototype.link=function(href,title,text){if(this.options.sanitize){try{var prot=decodeURIComponent(unescape(href)).replace(/[^\w:]/g,"").toLowerCase()}catch(e){return""}if(prot.indexOf("javascript:")===0||prot.indexOf("vbscript:")===0){return""}}var out='
    ";return out};Renderer.prototype.image=function(href,title,text){var out=''+text+'":">";return out};Renderer.prototype.text=function(text){return text};function Parser(options){this.tokens=[];this.token=null;this.options=options||marked.defaults;this.options.renderer=this.options.renderer||new Renderer;this.renderer=this.options.renderer;this.renderer.options=this.options}Parser.parse=function(src,options,renderer){var parser=new Parser(options,renderer);return parser.parse(src)};Parser.prototype.parse=function(src){this.inline=new InlineLexer(src.links,this.options,this.renderer);this.tokens=src.reverse();var out="";while(this.next()){out+=this.tok()}return out};Parser.prototype.next=function(){return this.token=this.tokens.pop()};Parser.prototype.peek=function(){return this.tokens[this.tokens.length-1]||0};Parser.prototype.parseText=function(){var body=this.token.text;while(this.peek().type==="text"){body+="\n"+this.next().text}return this.inline.output(body)};Parser.prototype.tok=function(){switch(this.token.type){case"space":{return""}case"hr":{return this.renderer.hr()}case"heading":{return this.renderer.heading(this.inline.output(this.token.text),this.token.depth,this.token.text)}case"code":{return this.renderer.code(this.token.text,this.token.lang,this.token.escaped)}case"table":{var header="",body="",i,row,cell,flags,j;cell="";for(i=0;i/g,">").replace(/"/g,""").replace(/'/g,"'")}function unescape(html){return html.replace(/&([#\w]+);/g,function(_,n){n=n.toLowerCase();if(n==="colon")return":";if(n.charAt(0)==="#"){return n.charAt(1)==="x"?String.fromCharCode(parseInt(n.substring(2),16)):String.fromCharCode(+n.substring(1))}return""})}function replace(regex,opt){regex=regex.source;opt=opt||"";return function self(name,val){if(!name)return new RegExp(regex,opt);val=val.source||val;val=val.replace(/(^|[^\[])\^/g,"$1");regex=regex.replace(name,val);return self}}function noop(){}noop.exec=noop;function merge(obj){var i=1,target,key;for(;iAn error occured:

    "+escape(e.message+"",true)+"
    "}throw e}}marked.options=marked.setOptions=function(opt){merge(marked.defaults,opt);return marked};marked.defaults={gfm:true,tables:true,breaks:false,pedantic:false,sanitize:false,sanitizer:null,mangle:true,smartLists:false,silent:false,highlight:null,langPrefix:"lang-",smartypants:false,headerPrefix:"",renderer:new Renderer,xhtml:false};marked.Parser=Parser;marked.parser=Parser.parse;marked.Renderer=Renderer;marked.Lexer=Lexer;marked.lexer=Lexer.lex;marked.InlineLexer=InlineLexer;marked.inlineLexer=InlineLexer.output;marked.parse=marked;if(typeof module!=="undefined"&&typeof exports==="object"){module.exports=marked}else if(typeof define==="function"&&define.amd){define(function(){return marked})}else{this.marked=marked}}).call(function(){return this||(typeof window!=="undefined"?window:global)}()); \ No newline at end of file diff --git a/public/build/assets/js/marked.min-c2a88705e2.js.br b/public/build/assets/js/marked.min-c2a88705e2.js.br new file mode 100644 index 00000000..73d34d61 Binary files /dev/null and b/public/build/assets/js/marked.min-c2a88705e2.js.br differ diff --git a/public/build/assets/js/marked.min-c2a88705e2.js.gz b/public/build/assets/js/marked.min-c2a88705e2.js.gz new file mode 100644 index 00000000..23d8aff6 Binary files /dev/null and b/public/build/assets/js/marked.min-c2a88705e2.js.gz differ diff --git a/public/build/assets/js/newnote-c1700073b7.js b/public/build/assets/js/newnote-c1700073b7.js new file mode 100644 index 00000000..2a26b261 --- /dev/null +++ b/public/build/assets/js/newnote-c1700073b7.js @@ -0,0 +1,284 @@ +if ('geolocation' in navigator) { + var button = document.querySelector('#locate'); + if (button.addEventListener) { + //if we have javascript, event listeners and geolocation, make the locate + //button clickable and add event + button.disabled = false; + button.addEventListener('click', getLocation); + } +} + +function getLocation() { + navigator.geolocation.getCurrentPosition(function (position) { + //the locate button has been clicked so add the places/map + addPlaces(position.coords.latitude, position.coords.longitude); + }); +} + +function addPlaces(latitude, longitude) { + //get the nearby places + fetch('/places/near/' + latitude + '/' + longitude, { + credentials: 'same-origin', + method: 'get' + }).then(function (response) { + return response.json(); + }).then(function (j) { + if (j.length > 0) { + var i; + var places = []; + for (i = 0; i < j.length; ++i) { + var latlng = parseLocation(j[i].location); + var name = j[i].name; + var slug = j[i].slug; + places.push([name, slug, latlng[0], latlng[1]]); + } + //add a map with the nearby places + addMap(latitude, longitude, places); + } else { + //add a map with just current location + addMap(latitude, longitude); + } + }).catch(function (err) { + console.log(err); + }); +} + +function addMap(latitude, longitude, places) { + //make places null if not supplied + if (arguments.length == 2) { + places = null; + } + var form = button.parentNode; + var div = document.createElement('div'); + div.setAttribute('id', 'map'); + //add the map div + form.appendChild(div); + L.mapbox.accessToken = 'pk.eyJ1Ijoiam9ubnliYXJuZXMiLCJhIjoiVlpndW1EYyJ9.aP9fxAqLKh7lj0LpFh5k1w'; + var map = L.mapbox.map('map', 'jonnybarnes.gnoihnim') + .setView([latitude, longitude], 15) + .addLayer(L.mapbox.tileLayer('jonnybarnes.gnoihnim', { + detectRetina: true, + })); + //add a marker for the current location + var marker = L.marker([latitude, longitude], { + draggable: true, + }).addTo(map); + //when the location marker is dragged, if the new place form elements exist + //update the lat/lng values + marker.on('dragend', function () { + var placeFormLatitude = document.querySelector('#place-latitude'); + if (placeFormLatitude !== null) { + placeFormLatitude.value = getLatitudeFromMapboxMarker(marker.getLatLng()); + } + var placeFormLongitude = document.querySelector('#place-longitude'); + if (placeFormLongitude !== null) { + placeFormLongitude.value = getLongitudeFromMapboxMarker(marker.getLatLng()); + } + }); + //create the + places.forEach(function (item, index, array) { + var option = document.createElement('option'); + option.setAttribute('value', item[1]); + var text = document.createTextNode(item[0]); + option.appendChild(text); + option.dataset.latitude = item[2]; + option.dataset.longitude = item[3]; + selectEl.appendChild(option); + var placeMarker = L.marker([item[2], item[3]], { + icon: L.mapbox.marker.icon({ + 'marker-size': 'large', + 'marker-symbol': 'building', + 'marker-color': '#fa0' + }) + }).addTo(map); + var name = 'Name: ' + item[0]; + placeMarker.bindPopup(name, { + closeButton: true + }); + placeMarker.on('click', function (e) { + map.panTo([item[2], item[3]]); + selectPlace(item[1]); + }); + }); + //add an event listener + selectEl.addEventListener('change', function () { + if (selectEl.value !== 'no-location') { + var placeLat = selectEl[selectEl.selectedIndex].dataset.latitude; + var placeLon = selectEl[selectEl.selectedIndex].dataset.longitude; + map.panTo([placeLat, placeLon]); + } + }); + } + //add a button to add a new place + var newLocButton = document.createElement('button'); + newLocButton.setAttribute('type', 'button'); + newLocButton.setAttribute('id', 'create-new-place'); + newLocButton.appendChild(document.createTextNode('Create New Place?')); + //the event listener + newLocButton.addEventListener('click', function() { + //add the form elements + var nameLabel = document.createElement('label'); + nameLabel.setAttribute('for', 'place-name'); + nameLabel.classList.add('place-label') + nameLabel.appendChild(document.createTextNode('Place Name:')); + var nameEl = document.createElement('input'); + nameEl.setAttribute('placeholder', 'Name'); + nameEl.setAttribute('name', 'place-name'); + nameEl.setAttribute('id', 'place-name'); + nameEl.setAttribute('type', 'text'); + var descLabel = document.createElement('label'); + descLabel.setAttribute('for', 'place-description'); + descLabel.classList.add('place-label'); + descLabel.appendChild(document.createTextNode('Place Description:')); + var descEl = document.createElement('input'); + descEl.setAttribute('placeholder', 'Description'); + descEl.setAttribute('name', 'place-description'); + descEl.setAttribute('id', 'place-description'); + descEl.setAttribute('type', 'text'); + var latLabel = document.createElement('label'); + latLabel.setAttribute('for', 'place-latitude'); + latLabel.classList.add('place-label'); + latLabel.appendChild(document.createTextNode('Place Latitude:')); + var latEl = document.createElement('input'); + latEl.setAttribute('name', 'place-latitude'); + latEl.setAttribute('id', 'place-latitude'); + latEl.setAttribute('type', 'text'); + latEl.value = getLatitudeFromMapboxMarker(marker.getLatLng()); + var lonLabel = document.createElement('label'); + lonLabel.setAttribute('for', 'place-longitude'); + lonLabel.classList.add('place-label'); + lonLabel.appendChild(document.createTextNode('Place Longitude:')); + var lonEl = document.createElement('input'); + lonEl.setAttribute('name', 'place-longitude'); + lonEl.setAttribute('id', 'place-longitude'); + lonEl.setAttribute('type', 'text'); + lonEl.value = getLongitudeFromMapboxMarker(marker.getLatLng()); + var placeSubmit = document.createElement('button'); + placeSubmit.setAttribute('id', 'place-submit'); + placeSubmit.setAttribute('value', 'Submit New Place'); + placeSubmit.setAttribute('name', 'place-submit'); + placeSubmit.setAttribute('type', 'button'); + placeSubmit.appendChild(document.createTextNode('Submit New Place')); + form.appendChild(nameLabel); + form.appendChild(nameEl); + form.appendChild(descLabel); + form.appendChild(descEl); + form.appendChild(latLabel); + form.appendChild(latEl); + form.appendChild(lonLabel); + form.appendChild(lonEl); + form.appendChild(placeSubmit); + //the event listener for the new place form + placeSubmit.addEventListener('click', function () { + //create the form data to send + var formData = new FormData(); + formData.append('place-name', document.querySelector('#place-name').value); + formData.append('place-description', document.querySelector('#place-description').value); + formData.append('place-latitude', document.querySelector('#place-latitude').value); + formData.append('place-longitude', document.querySelector('#place-longitude').value); + //post the new place + fetch('/places/new', { + //send cookies with the request + credentials: 'same-origin', + method: 'post', + body: formData + }) + .then(status) + .then(json) + .then(function (placeJson) { + //create the slug from the url + var urlParts = placeJson.split('/'); + var slug = urlParts.pop(); + //remove un-needed form elements + form.removeChild(document.querySelector('#place-name')); + form.removeChild(document.querySelector('#place-description')); + form.removeChild(document.querySelector('#place-latitude')); + form.removeChild(document.querySelector('#place-longitude')); + var labels = document.querySelectorAll('.place-label'); + for (var label of labels) { + form.removeChild(label); + } + form.removeChild(document.querySelector('#place-submit')); + form.removeChild(document.querySelector('#create-new-place')); + //remove location marker + map.removeLayer(marker); + //add place marker + var newOption = document.createElement('option'); + newOption.setAttribute('value', slug); + newOption.appendChild(document.createTextNode(placeJson['name'])); + newOption.dataset.latitude = placeJson['latitude']; + newOption.dataset.longitude = placeJson['longitude']; + selectEl.appendChild(newOption); + var newPlaceMarker = L.marker([placeJson['latitude'], placeJson['longitude']], { + icon: L.mapbox.marker.icon({ + 'marker-size': 'large', + 'marker-symbol': 'building', + 'marker-color': '#fa0' + }) + }).addTo(map); + var newName = 'Name: ' + placeJson['name']; + newPlaceMarker.bindPopup(newName, { + closeButton: true + }); + newPlaceMarker.on('click', function (e) { + map.panTo([placeJson['latitude'], placeJson['longitude']]); + selectPlace(slug); + }); + //make selected + selectPlace(slug); + }).catch(function (placeError) { + console.log(placeError); + }); + }) + }); + form.insertBefore(newLocButton, div); +} + +function parseLocation(point) { + var re = /\((.*)\)/; + var resultArray = re.exec(point); + var location = resultArray[1].split(' '); + + return [location[1], location[0]]; +} + +function selectPlace(slug) { + document.querySelector('select [value=' + slug + ']').selected = true; +} + +function getLatitudeFromMapboxMarker(latlng) { + var resultArray = /\((.*)\)/.exec(latlng); + var location = resultArray[1].split(' '); + + return location[0].replace(',', ''); +} + +function getLongitudeFromMapboxMarker(latlng) { + var resultArray = /\((.*)\)/.exec(latlng); + var location = resultArray[1].split(' '); + + return location[1]; +} + +function status(response) { + if (response.status >= 200 && response.status < 300) { + return Promise.resolve(response); + } else { + return Promise.reject(new Error(response.statusText)); + } +} + +function json(response) { + return response.json(); +} diff --git a/public/build/assets/js/newnote-c1700073b7.js.br b/public/build/assets/js/newnote-c1700073b7.js.br new file mode 100644 index 00000000..920c3a5b Binary files /dev/null and b/public/build/assets/js/newnote-c1700073b7.js.br differ diff --git a/public/build/assets/js/newnote-c1700073b7.js.gz b/public/build/assets/js/newnote-c1700073b7.js.gz new file mode 100644 index 00000000..b1d81ea5 Binary files /dev/null and b/public/build/assets/js/newnote-c1700073b7.js.gz differ diff --git a/public/build/assets/js/newplace-18722f800b.js b/public/build/assets/js/newplace-18722f800b.js new file mode 100644 index 00000000..166031a4 --- /dev/null +++ b/public/build/assets/js/newplace-18722f800b.js @@ -0,0 +1,45 @@ +var button = document.querySelector('#locate'); + +if (button.addEventListener) { + button.addEventListener('click', getLocation); +} else { + button.attachEvent('onclick', getLocation); +} + +function getLocation() { + if ('geolocation' in navigator) { + navigator.geolocation.getCurrentPosition(function(position) { + updateForm(position.coords.latitude, position.coords.longitude); + addMap(position.coords.latitude, position.coords.longitude); + }); + } else { + console.log('I need to do something when geoloaction isn’t available.'); + } +} + +function updateForm(latitude, longitude) { + var inputLatitude = document.querySelector('#latitude'); + var inputLongitude = document.querySelector('#longitude'); + inputLatitude.value = latitude; + inputLongitude.value = longitude; +} + +function addMap(latitude, longitude) { + var form = document.querySelector('form'); + var div = document.createElement('div'); + div.setAttribute('id', 'map'); + form.appendChild(div); + L.mapbox.accessToken = 'pk.eyJ1Ijoiam9ubnliYXJuZXMiLCJhIjoiVlpndW1EYyJ9.aP9fxAqLKh7lj0LpFh5k1w'; + var map = L.mapbox.map('map', 'jonnybarnes.gnoihnim') + .setView([latitude, longitude], 15) + .addLayer(L.mapbox.tileLayer('jonnybarnes.gnoihnim', { + detectRetina: true, + })); + var marker = L.marker([latitude, longitude], { + draggable: true, + }).addTo(map); + marker.on('dragend', function () { + var markerLocation = marker.getLatLng(); + updateForm(markerLocation.lat, markerLocation.lng); + }); +} diff --git a/public/build/assets/js/newplace-18722f800b.js.br b/public/build/assets/js/newplace-18722f800b.js.br new file mode 100644 index 00000000..1634bc75 Binary files /dev/null and b/public/build/assets/js/newplace-18722f800b.js.br differ diff --git a/public/build/assets/js/newplace-18722f800b.js.gz b/public/build/assets/js/newplace-18722f800b.js.gz new file mode 100644 index 00000000..db9cb1c7 Binary files /dev/null and b/public/build/assets/js/newplace-18722f800b.js.gz differ diff --git a/public/build/assets/js/prism-f6e997bc6d.js b/public/build/assets/js/prism-f6e997bc6d.js new file mode 100644 index 00000000..b00d099e --- /dev/null +++ b/public/build/assets/js/prism-f6e997bc6d.js @@ -0,0 +1,15 @@ +/* http://prismjs.com/download.html?themes=prism-dark&languages=markup+css+clike+javascript+git+http+markdown+php+php-extras+scss+sql&plugins=line-numbers+show-invisibles */ +var _self="undefined"!=typeof window?window:"undefined"!=typeof WorkerGlobalScope&&self instanceof WorkerGlobalScope?self:{},Prism=function(){var e=/\blang(?:uage)?-(?!\*)(\w+)\b/i,t=_self.Prism={util:{encode:function(e){return e instanceof n?new n(e.type,t.util.encode(e.content),e.alias):"Array"===t.util.type(e)?e.map(t.util.encode):e.replace(/&/g,"&").replace(/e.length)break e;if(!(d instanceof a)){u.lastIndex=0;var m=u.exec(d);if(m){c&&(f=m[1].length);var y=m.index-1+f,m=m[0].slice(f),v=m.length,k=y+v,b=d.slice(0,y+1),w=d.slice(k+1),N=[p,1];b&&N.push(b);var O=new a(l,g?t.tokenize(m,g):m,h);N.push(O),w&&N.push(w),Array.prototype.splice.apply(r,N)}}}}}return r},hooks:{all:{},add:function(e,n){var a=t.hooks.all;a[e]=a[e]||[],a[e].push(n)},run:function(e,n){var a=t.hooks.all[e];if(a&&a.length)for(var r,i=0;r=a[i++];)r(n)}}},n=t.Token=function(e,t,n){this.type=e,this.content=t,this.alias=n};if(n.stringify=function(e,a,r){if("string"==typeof e)return e;if("Array"===t.util.type(e))return e.map(function(t){return n.stringify(t,a,e)}).join("");var i={type:e.type,content:n.stringify(e.content,a,r),tag:"span",classes:["token",e.type],attributes:{},language:a,parent:r};if("comment"==i.type&&(i.attributes.spellcheck="true"),e.alias){var l="Array"===t.util.type(e.alias)?e.alias:[e.alias];Array.prototype.push.apply(i.classes,l)}t.hooks.run("wrap",i);var o="";for(var s in i.attributes)o+=s+'="'+(i.attributes[s]||"")+'"';return"<"+i.tag+' class="'+i.classes.join(" ")+'" '+o+">"+i.content+""},!_self.document)return _self.addEventListener?(_self.addEventListener("message",function(e){var n=JSON.parse(e.data),a=n.language,r=n.code;_self.postMessage(JSON.stringify(t.util.encode(t.tokenize(r,t.languages[a])))),_self.close()},!1),_self.Prism):_self.Prism;var a=document.getElementsByTagName("script");return a=a[a.length-1],a&&(t.filename=a.src,document.addEventListener&&!a.hasAttribute("data-manual")&&document.addEventListener("DOMContentLoaded",t.highlightAll)),_self.Prism}();"undefined"!=typeof module&&module.exports&&(module.exports=Prism);; +Prism.languages.markup={comment://,prolog:/<\?[\w\W]+?\?>/,doctype://,cdata://i,tag:{pattern:/<\/?[^\s>\/]+(?:\s+[^\s>\/=]+(?:=(?:("|')(?:\\\1|\\?(?!\1)[\w\W])*\1|[^\s'">=]+))?)*\s*\/?>/i,inside:{tag:{pattern:/^<\/?[^\s>\/]+/i,inside:{punctuation:/^<\/?/,namespace:/^[^\s>\/:]+:/}},"attr-value":{pattern:/=(?:('|")[\w\W]*?(\1)|[^\s>]+)/i,inside:{punctuation:/[=>"']/}},punctuation:/\/?>/,"attr-name":{pattern:/[^\s>\/]+/,inside:{namespace:/^[^\s>\/:]+:/}}}},entity:/&#?[\da-z]{1,8};/i},Prism.hooks.add("wrap",function(t){"entity"===t.type&&(t.attributes.title=t.content.replace(/&/,"&"))});; +Prism.languages.css={comment:/\/\*[\w\W]*?\*\//,atrule:{pattern:/@[\w-]+?.*?(;|(?=\s*\{))/i,inside:{rule:/@[\w-]+/}},url:/url\((?:(["'])(\\(?:\r\n|[\w\W])|(?!\1)[^\\\r\n])*\1|.*?)\)/i,selector:/[^\{\}\s][^\{\};]*?(?=\s*\{)/,string:/("|')(\\(?:\r\n|[\w\W])|(?!\1)[^\\\r\n])*\1/,property:/(\b|\B)[\w-]+(?=\s*:)/i,important:/\B!important\b/i,"function":/[-a-z0-9]+(?=\()/i,punctuation:/[(){};:]/},Prism.languages.css.atrule.inside.rest=Prism.util.clone(Prism.languages.css),Prism.languages.markup&&(Prism.languages.insertBefore("markup","tag",{style:{pattern:/[\w\W]*?<\/style>/i,inside:{tag:{pattern:/|<\/style>/i,inside:Prism.languages.markup.tag.inside},rest:Prism.languages.css},alias:"language-css"}}),Prism.languages.insertBefore("inside","attr-value",{"style-attr":{pattern:/\s*style=("|').*?\1/i,inside:{"attr-name":{pattern:/^\s*style/i,inside:Prism.languages.markup.tag.inside},punctuation:/^\s*=\s*['"]|['"]\s*$/,"attr-value":{pattern:/.+/i,inside:Prism.languages.css}},alias:"language-css"}},Prism.languages.markup.tag));; +Prism.languages.clike={comment:[{pattern:/(^|[^\\])\/\*[\w\W]*?\*\//,lookbehind:!0},{pattern:/(^|[^\\:])\/\/.*/,lookbehind:!0}],string:/("|')(\\(?:\r\n|[\s\S])|(?!\1)[^\\\r\n])*\1/,"class-name":{pattern:/((?:\b(?:class|interface|extends|implements|trait|instanceof|new)\s+)|(?:catch\s+\())[a-z0-9_\.\\]+/i,lookbehind:!0,inside:{punctuation:/(\.|\\)/}},keyword:/\b(if|else|while|do|for|return|in|instanceof|function|new|try|throw|catch|finally|null|break|continue)\b/,"boolean":/\b(true|false)\b/,"function":/[a-z0-9_]+(?=\()/i,number:/\b-?(0x[\dA-Fa-f]+|\d*\.?\d+([Ee]-?\d+)?)\b/,operator:/[-+]{1,2}|!|<=?|>=?|={1,3}|&{1,2}|\|?\||\?|\*|\/|~|\^|%/,punctuation:/[{}[\];(),.:]/};; +Prism.languages.javascript=Prism.languages.extend("clike",{keyword:/\b(as|async|await|break|case|catch|class|const|continue|debugger|default|delete|do|else|enum|export|extends|false|finally|for|from|function|get|if|implements|import|in|instanceof|interface|let|new|null|of|package|private|protected|public|return|set|static|super|switch|this|throw|true|try|typeof|var|void|while|with|yield)\b/,number:/\b-?(0x[\dA-Fa-f]+|0b[01]+|0o[0-7]+|\d*\.?\d+([Ee][+-]?\d+)?|NaN|Infinity)\b/,"function":/(?!\d)[a-z0-9_$]+(?=\()/i}),Prism.languages.insertBefore("javascript","keyword",{regex:{pattern:/(^|[^/])\/(?!\/)(\[.+?]|\\.|[^/\\\r\n])+\/[gimyu]{0,5}(?=\s*($|[\r\n,.;})]))/,lookbehind:!0}}),Prism.languages.insertBefore("javascript","class-name",{"template-string":{pattern:/`(?:\\`|\\?[^`])*`/,inside:{interpolation:{pattern:/\$\{[^}]+\}/,inside:{"interpolation-punctuation":{pattern:/^\$\{|\}$/,alias:"punctuation"},rest:Prism.languages.javascript}},string:/[\s\S]+/}}}),Prism.languages.markup&&Prism.languages.insertBefore("markup","tag",{script:{pattern:/[\w\W]*?<\/script>/i,inside:{tag:{pattern:/|<\/script>/i,inside:Prism.languages.markup.tag.inside},rest:Prism.languages.javascript},alias:"language-javascript"}});; +Prism.languages.git={comment:/^#.*$/m,string:/("|')(\\?.)*?\1/m,command:{pattern:/^.*\$ git .*$/m,inside:{parameter:/\s(--|-)\w+/m}},coord:/^@@.*@@$/m,deleted:/^-(?!-).+$/m,inserted:/^\+(?!\+).+$/m,commit_sha1:/^commit \w{40}$/m};; +Prism.languages.http={"request-line":{pattern:/^(POST|GET|PUT|DELETE|OPTIONS|PATCH|TRACE|CONNECT)\b\shttps?:\/\/\S+\sHTTP\/[0-9.]+/,inside:{property:/^\b(POST|GET|PUT|DELETE|OPTIONS|PATCH|TRACE|CONNECT)\b/,"attr-name":/:\w+/}},"response-status":{pattern:/^HTTP\/1.[01] [0-9]+.*/,inside:{property:/[0-9]+[A-Z\s-]+$/i}},keyword:/^[\w-]+:(?=.+)/m};var httpLanguages={"application/json":Prism.languages.javascript,"application/xml":Prism.languages.markup,"text/xml":Prism.languages.markup,"text/html":Prism.languages.markup};for(var contentType in httpLanguages)if(httpLanguages[contentType]){var options={};options[contentType]={pattern:new RegExp("(content-type:\\s*"+contentType+"[\\w\\W]*?)\\n\\n[\\w\\W]*","i"),lookbehind:!0,inside:{rest:httpLanguages[contentType]}},Prism.languages.insertBefore("http","keyword",options)}; +Prism.languages.markdown=Prism.languages.extend("markup",{}),Prism.languages.insertBefore("markdown","prolog",{blockquote:{pattern:/(^|\n)>(?:[\t ]*>)*/,lookbehind:!0,alias:"punctuation"},code:[{pattern:/(^|\n)(?: {4}|\t).+/,lookbehind:!0,alias:"keyword"},{pattern:/``.+?``|`[^`\n]+`/,alias:"keyword"}],title:[{pattern:/\w+.*\n(?:==+|--+)/,alias:"important",inside:{punctuation:/==+$|--+$/}},{pattern:/((?:^|\n)\s*)#+.+/,lookbehind:!0,alias:"important",inside:{punctuation:/^#+|#+$/}}],hr:{pattern:/((?:^|\n)\s*)([*-])([\t ]*\2){2,}(?=\s*(?:\n|$))/,lookbehind:!0,alias:"punctuation"},list:{pattern:/((?:^|\n)\s*)(?:[*+-]|\d+\.)(?=[\t ].)/,lookbehind:!0,alias:"punctuation"},"url-reference":{pattern:/!?\[[^\]]+\]:[\t ]+(?:\S+|<(?:[^>]|\\>)+>)(?:[\t ]+(?:"(?:[^"]|\\")*"|'(?:[^']|\\')*'|\((?:[^)]|\\\))*\)))?/,inside:{variable:{pattern:/^(!?\[)[^\]]+/,lookbehind:!0},string:/(?:"(?:[^"]|\\")*"|'(?:[^']|\\')*'|\((?:[^)]|\\\))*\))$/,punctuation:/[[\]\(\)<>:]/},alias:"url"},bold:{pattern:/(^|[^\\])(\*\*|__)(?:\n(?!\n)|.)+?\2/,lookbehind:!0,inside:{punctuation:/^\*\*|^__|\*\*\s*$|__\s*$/}},italic:{pattern:/(^|[^\\])(?:\*(?:\n(?!\n)|.)+?\*|_(?:\n(?!\n)|.)+?_)/,lookbehind:!0,inside:{punctuation:/^[*_]|[*_]$/}},url:{pattern:/!?\[[^\]]+\](?:\([^\s)]+(?:[\t ]+"(?:[^"]|\\")*")?\)| ?\[[^\]\n]*\])/,inside:{variable:{pattern:/(!?\[)[^\]]+(?=\]$)/,lookbehind:!0},string:{pattern:/"(?:[^"]|\\")*"(?=\)$)/}}}}),Prism.languages.markdown.bold.inside.url=Prism.util.clone(Prism.languages.markdown.url),Prism.languages.markdown.italic.inside.url=Prism.util.clone(Prism.languages.markdown.url),Prism.languages.markdown.bold.inside.italic=Prism.util.clone(Prism.languages.markdown.italic),Prism.languages.markdown.italic.inside.bold=Prism.util.clone(Prism.languages.markdown.bold);; +Prism.languages.php=Prism.languages.extend("clike",{keyword:/\b(and|or|xor|array|as|break|case|cfunction|class|const|continue|declare|default|die|do|else|elseif|enddeclare|endfor|endforeach|endif|endswitch|endwhile|extends|for|foreach|function|include|include_once|global|if|new|return|static|switch|use|require|require_once|var|while|abstract|interface|public|implements|private|protected|parent|throw|null|echo|print|trait|namespace|final|yield|goto|instanceof|finally|try|catch)\b/i,constant:/\b[A-Z0-9_]{2,}\b/,comment:{pattern:/(^|[^\\])(\/\*[\w\W]*?\*\/|(^|[^:])(\/\/).*?(\r?\n|$))/,lookbehind:!0}}),Prism.languages.insertBefore("php","class-name",{"shell-comment":{pattern:/(^|[^\\])#.*?(\r?\n|$)/,lookbehind:!0,alias:"comment"}}),Prism.languages.insertBefore("php","keyword",{delimiter:/(\?>|<\?php|<\?)/i,variable:/(\$\w+)\b/i,"package":{pattern:/(\\|namespace\s+|use\s+)[\w\\]+/,lookbehind:!0,inside:{punctuation:/\\/}}}),Prism.languages.insertBefore("php","operator",{property:{pattern:/(->)[\w]+/,lookbehind:!0}}),Prism.languages.markup&&(Prism.hooks.add("before-highlight",function(e){"php"===e.language&&(e.tokenStack=[],e.backupCode=e.code,e.code=e.code.replace(/(?:<\?php|<\?)[\w\W]*?(?:\?>)/gi,function(n){return e.tokenStack.push(n),"{{{PHP"+e.tokenStack.length+"}}}"}))}),Prism.hooks.add("before-insert",function(e){"php"===e.language&&(e.code=e.backupCode,delete e.backupCode)}),Prism.hooks.add("after-highlight",function(e){if("php"===e.language){for(var n,a=0;n=e.tokenStack[a];a++)e.highlightedCode=e.highlightedCode.replace("{{{PHP"+(a+1)+"}}}",Prism.highlight(n,e.grammar,"php"));e.element.innerHTML=e.highlightedCode}}),Prism.hooks.add("wrap",function(e){"php"===e.language&&"markup"===e.type&&(e.content=e.content.replace(/(\{\{\{PHP[0-9]+\}\}\})/g,'$1'))}),Prism.languages.insertBefore("php","comment",{markup:{pattern:/<[^?]\/?(.*?)>/,inside:Prism.languages.markup},php:/\{\{\{PHP[0-9]+\}\}\}/}));; +Prism.languages.insertBefore("php","variable",{"this":/\$this/,global:/\$_?(GLOBALS|SERVER|GET|POST|FILES|REQUEST|SESSION|ENV|COOKIE|HTTP_RAW_POST_DATA|argc|argv|php_errormsg|http_response_header)/,scope:{pattern:/\b[\w\\]+::/,inside:{keyword:/(static|self|parent)/,punctuation:/(::|\\)/}}});; +Prism.languages.scss=Prism.languages.extend("css",{comment:{pattern:/(^|[^\\])(\/\*[\w\W]*?\*\/|\/\/.*?(\r?\n|$))/,lookbehind:!0},atrule:{pattern:/@[\w-]+(?:\([^()]+\)|[^(])*?(?=\s+(\{|;))/i,inside:{rule:/@[\w-]+/}},url:/([-a-z]+-)*url(?=\()/i,selector:{pattern:/([^@;\{\}\(\)]?([^@;\{\}\(\)]|&|#\{\$[-_\w]+\})+)(?=\s*\{(\}|\s|[^\}]+(:|\{)[^\}]+))/m,inside:{placeholder:/%[-_\w]+/i}}}),Prism.languages.insertBefore("scss","atrule",{keyword:/@(if|else if|else|for|each|while|import|extend|debug|warn|mixin|include|function|return|content)|(?=@for\s+\$[-_\w]+\s)+from/i}),Prism.languages.insertBefore("scss","property",{variable:/((\$[-_\w]+)|(#\{\$[-_\w]+\}))/i}),Prism.languages.insertBefore("scss","function",{placeholder:{pattern:/%[-_\w]+/i,alias:"selector"},statement:/\B!(default|optional)\b/i,"boolean":/\b(true|false)\b/,"null":/\b(null)\b/,operator:/\s+([-+]{1,2}|={1,2}|!=|\|?\||\?|\*|\/|%)\s+/}),Prism.languages.scss.atrule.inside.rest=Prism.util.clone(Prism.languages.scss);; +Prism.languages.sql={comment:{pattern:/(^|[^\\])(\/\*[\w\W]*?\*\/|((--)|(\/\/)|#).*?(\r?\n|$))/,lookbehind:!0},string:{pattern:/(^|[^@])("|')(\\?[\s\S])*?\2/,lookbehind:!0},variable:/@[\w.$]+|@("|'|`)(\\?[\s\S])+?\1/,"function":/\b(?:COUNT|SUM|AVG|MIN|MAX|FIRST|LAST|UCASE|LCASE|MID|LEN|ROUND|NOW|FORMAT)(?=\s*\()/i,keyword:/\b(?:ACTION|ADD|AFTER|ALGORITHM|ALTER|ANALYZE|APPLY|AS|ASC|AUTHORIZATION|BACKUP|BDB|BEGIN|BERKELEYDB|BIGINT|BINARY|BIT|BLOB|BOOL|BOOLEAN|BREAK|BROWSE|BTREE|BULK|BY|CALL|CASCADE|CASCADED|CASE|CHAIN|CHAR VARYING|CHARACTER VARYING|CHECK|CHECKPOINT|CLOSE|CLUSTERED|COALESCE|COLUMN|COLUMNS|COMMENT|COMMIT|COMMITTED|COMPUTE|CONNECT|CONSISTENT|CONSTRAINT|CONTAINS|CONTAINSTABLE|CONTINUE|CONVERT|CREATE|CROSS|CURRENT|CURRENT_DATE|CURRENT_TIME|CURRENT_TIMESTAMP|CURRENT_USER|CURSOR|DATA|DATABASE|DATABASES|DATETIME|DBCC|DEALLOCATE|DEC|DECIMAL|DECLARE|DEFAULT|DEFINER|DELAYED|DELETE|DENY|DESC|DESCRIBE|DETERMINISTIC|DISABLE|DISCARD|DISK|DISTINCT|DISTINCTROW|DISTRIBUTED|DO|DOUBLE|DOUBLE PRECISION|DROP|DUMMY|DUMP|DUMPFILE|DUPLICATE KEY|ELSE|ENABLE|ENCLOSED BY|END|ENGINE|ENUM|ERRLVL|ERRORS|ESCAPE|ESCAPED BY|EXCEPT|EXEC|EXECUTE|EXIT|EXPLAIN|EXTENDED|FETCH|FIELDS|FILE|FILLFACTOR|FIRST|FIXED|FLOAT|FOLLOWING|FOR|FOR EACH ROW|FORCE|FOREIGN|FREETEXT|FREETEXTTABLE|FROM|FULL|FUNCTION|GEOMETRY|GEOMETRYCOLLECTION|GLOBAL|GOTO|GRANT|GROUP|HANDLER|HASH|HAVING|HOLDLOCK|IDENTITY|IDENTITY_INSERT|IDENTITYCOL|IF|IGNORE|IMPORT|INDEX|INFILE|INNER|INNODB|INOUT|INSERT|INT|INTEGER|INTERSECT|INTO|INVOKER|ISOLATION LEVEL|JOIN|KEY|KEYS|KILL|LANGUAGE SQL|LAST|LEFT|LIMIT|LINENO|LINES|LINESTRING|LOAD|LOCAL|LOCK|LONGBLOB|LONGTEXT|MATCH|MATCHED|MEDIUMBLOB|MEDIUMINT|MEDIUMTEXT|MERGE|MIDDLEINT|MODIFIES SQL DATA|MODIFY|MULTILINESTRING|MULTIPOINT|MULTIPOLYGON|NATIONAL|NATIONAL CHAR VARYING|NATIONAL CHARACTER|NATIONAL CHARACTER VARYING|NATIONAL VARCHAR|NATURAL|NCHAR|NCHAR VARCHAR|NEXT|NO|NO SQL|NOCHECK|NOCYCLE|NONCLUSTERED|NULLIF|NUMERIC|OF|OFF|OFFSETS|ON|OPEN|OPENDATASOURCE|OPENQUERY|OPENROWSET|OPTIMIZE|OPTION|OPTIONALLY|ORDER|OUT|OUTER|OUTFILE|OVER|PARTIAL|PARTITION|PERCENT|PIVOT|PLAN|POINT|POLYGON|PRECEDING|PRECISION|PREV|PRIMARY|PRINT|PRIVILEGES|PROC|PROCEDURE|PUBLIC|PURGE|QUICK|RAISERROR|READ|READS SQL DATA|READTEXT|REAL|RECONFIGURE|REFERENCES|RELEASE|RENAME|REPEATABLE|REPLICATION|REQUIRE|RESTORE|RESTRICT|RETURN|RETURNS|REVOKE|RIGHT|ROLLBACK|ROUTINE|ROWCOUNT|ROWGUIDCOL|ROWS?|RTREE|RULE|SAVE|SAVEPOINT|SCHEMA|SELECT|SERIAL|SERIALIZABLE|SESSION|SESSION_USER|SET|SETUSER|SHARE MODE|SHOW|SHUTDOWN|SIMPLE|SMALLINT|SNAPSHOT|SOME|SONAME|START|STARTING BY|STATISTICS|STATUS|STRIPED|SYSTEM_USER|TABLE|TABLES|TABLESPACE|TEMP(?:ORARY)?|TEMPTABLE|TERMINATED BY|TEXT|TEXTSIZE|THEN|TIMESTAMP|TINYBLOB|TINYINT|TINYTEXT|TO|TOP|TRAN|TRANSACTION|TRANSACTIONS|TRIGGER|TRUNCATE|TSEQUAL|TYPE|TYPES|UNBOUNDED|UNCOMMITTED|UNDEFINED|UNION|UNPIVOT|UPDATE|UPDATETEXT|USAGE|USE|USER|USING|VALUE|VALUES|VARBINARY|VARCHAR|VARCHARACTER|VARYING|VIEW|WAITFOR|WARNINGS|WHEN|WHERE|WHILE|WITH|WITH ROLLUP|WITHIN|WORK|WRITE|WRITETEXT)\b/i,"boolean":/\b(?:TRUE|FALSE|NULL)\b/i,number:/\b-?(0x)?\d*\.?[\da-f]+\b/,operator:/\b(?:ALL|AND|ANY|BETWEEN|EXISTS|IN|LIKE|NOT|OR|IS|UNIQUE|CHARACTER SET|COLLATE|DIV|OFFSET|REGEXP|RLIKE|SOUNDS LIKE|XOR)\b|[-+]|!|[=<>]{1,2}|(&){1,2}|\|?\||\?|\*|\//i,punctuation:/[;[\]()`,.]/};; +Prism.hooks.add("complete",function(e){if(e.code){var t=e.element.parentNode,s=/\s*\bline-numbers\b\s*/;if(t&&/pre/i.test(t.nodeName)&&(s.test(t.className)||s.test(e.element.className))&&!e.element.querySelector(".line-numbers-rows")){s.test(e.element.className)&&(e.element.className=e.element.className.replace(s,"")),s.test(t.className)||(t.className+=" line-numbers");var a,n=e.code.match(/\n(?!$)/g).length+1,l=new Array(n+1);l=l.join(""),a=document.createElement("span"),a.className="line-numbers-rows",a.innerHTML=l,t.hasAttribute("data-start")&&(t.style.counterReset="linenumber "+(parseInt(t.getAttribute("data-start"),10)-1)),e.element.appendChild(a)}}});; +!function(){if(window.Prism)for(var r in Prism.languages){var g=Prism.languages[r];g.tab=/\t/g,g.crlf=/\r\n/g,g.lf=/\n/g,g.cr=/\r/g}}();; diff --git a/public/build/assets/js/prism-f6e997bc6d.js.br b/public/build/assets/js/prism-f6e997bc6d.js.br new file mode 100644 index 00000000..2cef61aa Binary files /dev/null and b/public/build/assets/js/prism-f6e997bc6d.js.br differ diff --git a/public/build/assets/js/prism-f6e997bc6d.js.gz b/public/build/assets/js/prism-f6e997bc6d.js.gz new file mode 100644 index 00000000..aaeb5ca2 Binary files /dev/null and b/public/build/assets/js/prism-f6e997bc6d.js.gz differ diff --git a/public/build/assets/js/store2.min-c4daa8f871.js b/public/build/assets/js/store2.min-c4daa8f871.js new file mode 100644 index 00000000..72aff0ab --- /dev/null +++ b/public/build/assets/js/store2.min-c4daa8f871.js @@ -0,0 +1,5 @@ +/*! store2 - v2.3.2 - 2015-10-27 +* Copyright (c) 2015 Nathan Bubna; Licensed MIT, GPL */ + +!function(a,b){var c={version:"2.3.2",areas:{},apis:{},inherit:function(a,b){for(var c in a)b.hasOwnProperty(c)||(b[c]=a[c]);return b},stringify:function(a){return void 0===a||"function"==typeof a?a+"":JSON.stringify(a)},parse:function(a){try{return JSON.parse(a)}catch(b){return a}},fn:function(a,b){c.storeAPI[a]=b;for(var d in c.apis)c.apis[d][a]=b},get:function(a,b){return a.getItem(b)},set:function(a,b,c){a.setItem(b,c)},remove:function(a,b){a.removeItem(b)},key:function(a,b){return a.key(b)},length:function(a){return a.length},clear:function(a){a.clear()},Store:function(a,b,d){var e=c.inherit(c.storeAPI,function(a,b,c){return 0===arguments.length?e.getAll():void 0!==b?e.set(a,b,c):"string"==typeof a||"number"==typeof a?e.get(a):a?e.setAll(a,b):e.clear()});e._id=a;try{var f="_safariPrivate_";b.setItem(f,"sucks"),e._area=b,b.removeItem(f)}catch(g){}return e._area||(e._area=c.inherit(c.storageAPI,{items:{},name:"fake"})),e._ns=d||"",c.areas[a]||(c.areas[a]=e._area),c.apis[e._ns+e._id]||(c.apis[e._ns+e._id]=e),e},storeAPI:{area:function(a,b){var d=this[a];return d&&d.area||(d=c.Store(a,b,this._ns),this[a]||(this[a]=d)),d},namespace:function(a,b){if(!a)return this._ns?this._ns.substring(0,this._ns.length-1):"";var d=a,e=this[d];return e&&e.namespace||(e=c.Store(this._id,this._area,this._ns+d+"."),this[d]||(this[d]=e),b||e.area("session",c.areas.session)),e},isFake:function(){return"fake"===this._area.name},toString:function(){return"store"+(this._ns?"."+this.namespace():"")+"["+this._id+"]"},has:function(a){return this._area.has?this._area.has(this._in(a)):!!(this._in(a)in this._area)},size:function(){return this.keys().length},each:function(a,b){for(var d=0,e=c.length(this._area);e>d;d++){var f=this._out(c.key(this._area,d));if(void 0!==f&&a.call(this,f,b||this.get(f))===!1)break;e>c.length(this._area)&&(e--,d--)}return b||this},keys:function(){return this.each(function(a,b){b.push(a)},[])},get:function(a,b){var d=c.get(this._area,this._in(a));return null!==d?c.parse(d):b||d},getAll:function(){return this.each(function(a,b){b[a]=this.get(a)},{})},set:function(a,b,d){var e=this.get(a);return null!=e&&d===!1?b:c.set(this._area,this._in(a),c.stringify(b),d)||e},setAll:function(a,b){var c,d;for(var e in a)d=a[e],this.set(e,d,b)!==d&&(c=!0);return c},remove:function(a){var b=this.get(a);return c.remove(this._area,this._in(a)),b},clear:function(){return this._ns?this.each(function(a){c.remove(this._area,this._in(a))},1):c.clear(this._area),this},clearAll:function(){var a=this._area;for(var b in c.areas)c.areas.hasOwnProperty(b)&&(this._area=c.areas[b],this.clear());return this._area=a,this},_in:function(a){return"string"!=typeof a&&(a=c.stringify(a)),this._ns?this._ns+a:a},_out:function(a){return this._ns?a&&0===a.indexOf(this._ns)?a.substring(this._ns.length):void 0:a}},storageAPI:{length:0,has:function(a){return this.items.hasOwnProperty(a)},key:function(a){var b=0;for(var c in this.items)if(this.has(c)&&a===b++)return c},setItem:function(a,b){this.has(a)||this.length++,this.items[a]=b},removeItem:function(a){this.has(a)&&(delete this.items[a],this.length--)},getItem:function(a){return this.has(a)?this.items[a]:null},clear:function(){for(var a in this.list)this.removeItem(a)},toString:function(){return this.length+" items in "+this.name+"Storage"}}};a.store&&(c.conflict=a.store);var d=c.Store("local",function(){try{return localStorage}catch(a){}}());d.local=d,d._=c,d.area("session",function(){try{return sessionStorage}catch(a){}}()),a.store=d,"function"==typeof b&&void 0!==b.amd?b(function(){return d}):"undefined"!=typeof module&&module.exports&&(module.exports=d)}(this,this.define); +//# sourceMappingURL=store2.min.js.map \ No newline at end of file diff --git a/public/build/assets/js/store2.min-c4daa8f871.js.br b/public/build/assets/js/store2.min-c4daa8f871.js.br new file mode 100644 index 00000000..49dda19b Binary files /dev/null and b/public/build/assets/js/store2.min-c4daa8f871.js.br differ diff --git a/public/build/assets/js/store2.min-c4daa8f871.js.gz b/public/build/assets/js/store2.min-c4daa8f871.js.gz new file mode 100644 index 00000000..657123f1 Binary files /dev/null and b/public/build/assets/js/store2.min-c4daa8f871.js.gz differ diff --git a/public/build/rev-manifest.json b/public/build/rev-manifest.json new file mode 100644 index 00000000..1ca6076b --- /dev/null +++ b/public/build/rev-manifest.json @@ -0,0 +1,18 @@ +{ + "assets/css/alertify.css": "assets/css/alertify-d84546f82d.css", + "assets/css/global.css": "assets/css/global-ef9dfef096.css", + "assets/css/prism.css": "assets/css/prism-5c98941a94.css", + "assets/css/projects.css": "assets/css/projects-d945298e4f.css", + "assets/css/sanitize.min.css": "assets/css/sanitize.min-535bccd783.css", + "assets/js/Autolinker.min.js": "assets/js/Autolinker.min-b46556773a.js", + "assets/js/alertify.js": "assets/js/alertify-269e23cb46.js", + "assets/js/fetch.js": "assets/js/fetch-5e9040330a.js", + "assets/js/form-save.js": "assets/js/form-save-7849d1a5f3.js", + "assets/js/links.js": "assets/js/links-ea4c99f585.js", + "assets/js/maps.js": "assets/js/maps-ffa37774ae.js", + "assets/js/marked.min.js": "assets/js/marked.min-c2a88705e2.js", + "assets/js/newnote.js": "assets/js/newnote-c1700073b7.js", + "assets/js/newplace.js": "assets/js/newplace-18722f800b.js", + "assets/js/prism.js": "assets/js/prism-f6e997bc6d.js", + "assets/js/store2.min.js": "assets/js/store2.min-c4daa8f871.js" +} \ No newline at end of file diff --git a/public/favicon.ico b/public/favicon.ico new file mode 100644 index 00000000..e69de29b diff --git a/public/index.php b/public/index.php new file mode 100644 index 00000000..c5820533 --- /dev/null +++ b/public/index.php @@ -0,0 +1,58 @@ + + */ + +/* +|-------------------------------------------------------------------------- +| Register The Auto Loader +|-------------------------------------------------------------------------- +| +| Composer provides a convenient, automatically generated class loader for +| our application. We just need to utilize it! We'll simply require it +| into the script here so that we don't have to worry about manual +| loading any of our classes later on. It feels nice to relax. +| +*/ + +require __DIR__.'/../bootstrap/autoload.php'; + +/* +|-------------------------------------------------------------------------- +| Turn On The Lights +|-------------------------------------------------------------------------- +| +| We need to illuminate PHP development, so let us turn on the lights. +| This bootstraps the framework and gets it ready for use, then it +| will load up this application so that we can run it and send +| the responses back to the browser and delight our users. +| +*/ + +$app = require_once __DIR__.'/../bootstrap/app.php'; + +/* +|-------------------------------------------------------------------------- +| Run The Application +|-------------------------------------------------------------------------- +| +| Once we have the application, we can handle the incoming request +| through the kernel, and send the associated response back to +| the client's browser allowing them to enjoy the creative +| and wonderful application we have prepared for them. +| +*/ + +$kernel = $app->make(Illuminate\Contracts\Http\Kernel::class); + +$response = $kernel->handle( + $request = Illuminate\Http\Request::capture() +); + +$response->send(); + +$kernel->terminate($request, $response); diff --git a/public/media/.gitignore b/public/media/.gitignore new file mode 100644 index 00000000..c96a04f0 --- /dev/null +++ b/public/media/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore \ No newline at end of file diff --git a/public/robots.txt b/public/robots.txt new file mode 100644 index 00000000..eb053628 --- /dev/null +++ b/public/robots.txt @@ -0,0 +1,2 @@ +User-agent: * +Disallow: diff --git a/public/web.config b/public/web.config new file mode 100644 index 00000000..624c1760 --- /dev/null +++ b/public/web.config @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/readme.md b/readme.md new file mode 100644 index 00000000..38b34928 --- /dev/null +++ b/readme.md @@ -0,0 +1,5 @@ +# jonnybarnes.uk + +This is the code that runs my website, [jonnybarnes.uk](https://jonnybarnes.uk). + +It’s not quite ready for anyone else, but it will be soon :) diff --git a/resources/assets/sass/app.scss b/resources/assets/sass/app.scss new file mode 100644 index 00000000..bb76e29c --- /dev/null +++ b/resources/assets/sass/app.scss @@ -0,0 +1,2 @@ +// @import "node_modules/bootstrap-sass/assets/stylesheets/bootstrap"; + diff --git a/resources/assets/sass/components/colours.scss b/resources/assets/sass/components/colours.scss new file mode 100644 index 00000000..61ad3735 --- /dev/null +++ b/resources/assets/sass/components/colours.scss @@ -0,0 +1,12 @@ +//colours.scss +body { + color: $base03; +} + +header a { + color: $base03; +} + +a { + color: $blue; +} \ No newline at end of file diff --git a/resources/assets/sass/components/fonts.scss b/resources/assets/sass/components/fonts.scss new file mode 100644 index 00000000..744735df --- /dev/null +++ b/resources/assets/sass/components/fonts.scss @@ -0,0 +1,41 @@ +//fonts.scss + +body { + text-rendering: optimizeLegibility; + font-feature-settings: "liga"; + font-family: $font-stack-body; + font-size: 1.2em; +} + +#topheader h1 { + font-family: $font-stack-body; +} + +h1 { + font-family: $font-stack-headers; +} + +#topheader a { + text-decoration: none; +} + +nav { + font-feature-settings: "dlig"; +} + +article header h1 a { + text-decoration: none; +} + +article div a { + text-decoration: none; +} + +footer { + font-size: 0.8em; +} + +.emoji { + width: auto; + height: 1em; +} diff --git a/resources/assets/sass/components/forms.scss b/resources/assets/sass/components/forms.scss new file mode 100644 index 00000000..095c423c --- /dev/null +++ b/resources/assets/sass/components/forms.scss @@ -0,0 +1,45 @@ +//forms.scss + +form { + width: 100%; +} + +fieldset { + min-width: 0; + width: 100%; +} + +input[type="text"], input[type="file"], textarea { + width: 100%; +} + +input, button, textarea { + -webkit-appearance: none; + -moz-appearance: none; + background-color: $base03; + color: $base3; + border: 1px solid $base3; + border-radius: 4px; +} + +button:hover { + transition: 0.5s ease-in-out; + background-color: $base3; + color: $base03; +} + +button:disabled { + background-color: $base1; + color: $base03; +} + +input[type="checkbox"] { + -webkit-appearance: checkbox; + -moz-appearance: checkbox; +} + +#photo { + background: inherit; + color: inherit; + border: none; +} diff --git a/resources/assets/sass/components/twitter.scss b/resources/assets/sass/components/twitter.scss new file mode 100644 index 00000000..8eb8a6a5 --- /dev/null +++ b/resources/assets/sass/components/twitter.scss @@ -0,0 +1,9 @@ +//twitter.scss + +.twitter-tweet-rendered { + margin-bottom: 0 !important; +} + +.twitter-tweet-rendered + .note { + margin-top: 0; +} diff --git a/resources/assets/sass/global.scss b/resources/assets/sass/global.scss new file mode 100644 index 00000000..bc2e0534 --- /dev/null +++ b/resources/assets/sass/global.scss @@ -0,0 +1,41 @@ +//global.scss + +//variables +$font-stack-body: "leitura-news", serif; +$font-stack-headers: "prenton", sans-serif; + +//solarized variables TERMCOL +$base03: #002b36;//brblack +$base02: #073642;//black +$base01: #586e75;//brgreen +$base00: #657b83;//bryellow +$base0: #839496;//brblue +$base1: #93a1a1;//brcyan +$base2: #eee8d5;//white +$base3: #fdf6e3;//brwhite +$yellow: #b58900; +$orange: #cb4b16; +$red: #dc322f; +$magenta: #d33682; +$violet: #6c71c4; +$blue: #268bd2; +$cyan: #2aa198; +$green: #859900; + +//global styles +html { + background: url('/assets/img/escheresque.png'); +} + +.map { + height: 150px; +} + +//layout +@import "layout"; + +//components +@import "components/fonts"; +@import "components/colours"; +@import "components/forms"; +@import "components/twitter"; diff --git a/resources/assets/sass/layout.scss b/resources/assets/sass/layout.scss new file mode 100644 index 00000000..c80cf830 --- /dev/null +++ b/resources/assets/sass/layout.scss @@ -0,0 +1,197 @@ +//layout.scss + +//boxes +html { + box-sizing: border-box; +} + +*, *:before, *:after { + box-sizing: inherit; +} + +#topheader { + display: flex; + flex-flow: row; +} + +#topheader a { + padding: 0.5em 1em; +} + +nav { + padding-top: 0.5em; +} + +.social-list { + padding-left: 2em; +} + +.note { + background-color: $base2; + box-shadow: 0 0 10px 2px $base1; + padding: 0.5em 0.5em; + margin-top: 1em; +} + +.note:after { + content: " "; + display: block; + height: 0; + clear: both; +} + +.note a { + word-wrap: break-word; +} + +.note .e-content p:first-child { + margin-top: 0; +} + +.note-metadata { + width: 100%; +} + +.social-links { + float: right; +} + +.social-links a { + text-decoration: none; +} + +.icon { + width: auto; + height: 1em; + fill: $blue; +} + +.reply { + margin-left: 2em; + margin-right: 2em; + font-size: 0.8em; + padding: 0.5em 0.5em; +} + +.reply-to { + margin-left: 2em; + margin-right: 2em; + font-size: 0.8em; + padding-top: 2em; +} + +.reply-to + .note { + margin-top: 0.3em; +} + +.mini-h-card { + border-radius: 2px; + border: 1px solid $base01; + padding: 0 0.2em; + text-decoration: none; + margin-right: 5px; + white-space: nowrap; +} + +.mini-h-card img { + height: 1.26em; + display: inline; + border-radius: 2px; + vertical-align: text-bottom; +} + +.like-photo { + height: 1.26em; +} + +.reply .e-content { + margin-top: 0.5em; + padding-left: 0.5em; +} + +.notes-subtitle { + font-size: 1em; +} + +.note-photo { + width: 100%; + height: auto; + image-orientation: from-image; +} + +//articles + +article header { + margin-top: 0.5em; + margin-bottom: 0.8em; +} + +.post-info { + font-size: 0.8em; + font-style: italic; + margin-top: -0.8em; +} + +//contacts +.contact { + position: relative; +} + +.contact-links { + list-style-type: none; +} + +.contact img { + height: auto; + width: 2em; + position: absolute; + top: 0; + left: 0; +} + +.contact-info { + margin-left: 2em; +} + +#map { + height: 300px; +} + +/* media queries */ +@media (min-width: 700px) { + main { + margin-left: 10em; + margin-right: 10em; + } + + footer { + margin-left: 13em; + margin-right: 13em; + } + + .youtube { + width: 640px; + height: 360px; + } +} + +@media (max-width: 699px) { + main { + margin-left: 10px; + margin-right: 10px; + } + + article { + word-wrap: break-word; + } + + footer { + margin-left: 15px; + margin-right: 15px; + } + + .youtube { + width: 100%; + height: auto; + } +} diff --git a/resources/lang/en/auth.php b/resources/lang/en/auth.php new file mode 100644 index 00000000..e5506df2 --- /dev/null +++ b/resources/lang/en/auth.php @@ -0,0 +1,19 @@ + 'These credentials do not match our records.', + 'throttle' => 'Too many login attempts. Please try again in :seconds seconds.', + +]; diff --git a/resources/lang/en/pagination.php b/resources/lang/en/pagination.php new file mode 100644 index 00000000..fcab34b2 --- /dev/null +++ b/resources/lang/en/pagination.php @@ -0,0 +1,19 @@ + '« Previous', + 'next' => 'Next »', + +]; diff --git a/resources/lang/en/passwords.php b/resources/lang/en/passwords.php new file mode 100644 index 00000000..e5544d20 --- /dev/null +++ b/resources/lang/en/passwords.php @@ -0,0 +1,22 @@ + 'Passwords must be at least six characters and match the confirmation.', + 'reset' => 'Your password has been reset!', + 'sent' => 'We have e-mailed your password reset link!', + 'token' => 'This password reset token is invalid.', + 'user' => "We can't find a user with that e-mail address.", + +]; diff --git a/resources/lang/en/validation.php b/resources/lang/en/validation.php new file mode 100644 index 00000000..b1e61204 --- /dev/null +++ b/resources/lang/en/validation.php @@ -0,0 +1,113 @@ + 'The :attribute must be accepted.', + 'active_url' => 'The :attribute is not a valid URL.', + 'after' => 'The :attribute must be a date after :date.', + 'alpha' => 'The :attribute may only contain letters.', + 'alpha_dash' => 'The :attribute may only contain letters, numbers, and dashes.', + 'alpha_num' => 'The :attribute may only contain letters and numbers.', + 'array' => 'The :attribute must be an array.', + 'before' => 'The :attribute must be a date before :date.', + 'between' => [ + 'numeric' => 'The :attribute must be between :min and :max.', + 'file' => 'The :attribute must be between :min and :max kilobytes.', + 'string' => 'The :attribute must be between :min and :max characters.', + 'array' => 'The :attribute must have between :min and :max items.', + ], + 'boolean' => 'The :attribute field must be true or false.', + 'confirmed' => 'The :attribute confirmation does not match.', + 'date' => 'The :attribute is not a valid date.', + 'date_format' => 'The :attribute does not match the format :format.', + 'different' => 'The :attribute and :other must be different.', + 'digits' => 'The :attribute must be :digits digits.', + 'digits_between' => 'The :attribute must be between :min and :max digits.', + 'distinct' => 'The :attribute field has a duplicate value.', + 'email' => 'The :attribute must be a valid email address.', + 'exists' => 'The selected :attribute is invalid.', + 'filled' => 'The :attribute field is required.', + 'image' => 'The :attribute must be an image.', + 'in' => 'The selected :attribute is invalid.', + 'in_array' => 'The :attribute field does not exist in :other.', + 'integer' => 'The :attribute must be an integer.', + 'ip' => 'The :attribute must be a valid IP address.', + 'json' => 'The :attribute must be a valid JSON string.', + 'max' => [ + 'numeric' => 'The :attribute may not be greater than :max.', + 'file' => 'The :attribute may not be greater than :max kilobytes.', + 'string' => 'The :attribute may not be greater than :max characters.', + 'array' => 'The :attribute may not have more than :max items.', + ], + 'mimes' => 'The :attribute must be a file of type: :values.', + 'min' => [ + 'numeric' => 'The :attribute must be at least :min.', + 'file' => 'The :attribute must be at least :min kilobytes.', + 'string' => 'The :attribute must be at least :min characters.', + 'array' => 'The :attribute must have at least :min items.', + ], + 'not_in' => 'The selected :attribute is invalid.', + 'numeric' => 'The :attribute must be a number.', + 'present' => 'The :attribute field must be present.', + 'regex' => 'The :attribute format is invalid.', + 'required' => 'The :attribute field is required.', + 'required_if' => 'The :attribute field is required when :other is :value.', + 'required_unless' => 'The :attribute field is required unless :other is in :values.', + 'required_with' => 'The :attribute field is required when :values is present.', + 'required_with_all' => 'The :attribute field is required when :values is present.', + 'required_without' => 'The :attribute field is required when :values is not present.', + 'required_without_all' => 'The :attribute field is required when none of :values are present.', + 'same' => 'The :attribute and :other must match.', + 'size' => [ + 'numeric' => 'The :attribute must be :size.', + 'file' => 'The :attribute must be :size kilobytes.', + 'string' => 'The :attribute must be :size characters.', + 'array' => 'The :attribute must contain :size items.', + ], + 'string' => 'The :attribute must be a string.', + 'timezone' => 'The :attribute must be a valid zone.', + 'unique' => 'The :attribute has already been taken.', + 'url' => 'The :attribute format is invalid.', + + /* + |-------------------------------------------------------------------------- + | Custom Validation Language Lines + |-------------------------------------------------------------------------- + | + | Here you may specify custom validation messages for attributes using the + | convention "attribute.rule" to name the lines. This makes it quick to + | specify a specific custom language line for a given attribute rule. + | + */ + + 'custom' => [ + 'attribute-name' => [ + 'rule-name' => 'custom-message', + ], + ], + + /* + |-------------------------------------------------------------------------- + | Custom Validation Attributes + |-------------------------------------------------------------------------- + | + | The following language lines are used to swap attribute place-holders + | with something more reader friendly such as E-Mail Address instead + | of "email". This simply helps us make messages a little cleaner. + | + */ + + 'attributes' => [], + +]; diff --git a/resources/views/admin/deletearticle.blade.php b/resources/views/admin/deletearticle.blade.php new file mode 100644 index 00000000..95ab35d3 --- /dev/null +++ b/resources/views/admin/deletearticle.blade.php @@ -0,0 +1,13 @@ +@extends('master') + +@section('title') +Delete Article? « Admin CP +@stop + +@section('content') +
    + +
    + +
    +@stop diff --git a/resources/views/admin/deletearticlesuccess.blade.php b/resources/views/admin/deletearticlesuccess.blade.php new file mode 100644 index 00000000..1eac9ce1 --- /dev/null +++ b/resources/views/admin/deletearticlesuccess.blade.php @@ -0,0 +1,9 @@ +@extends('master') + +@section('title') +Article Deleted « Admin CP +@stop + +@section('content') +

    You have successfully deletd the article with id: {{ $id }}

    +@stop diff --git a/resources/views/admin/deleteclientsuccess.blade.php b/resources/views/admin/deleteclientsuccess.blade.php new file mode 100644 index 00000000..892474a3 --- /dev/null +++ b/resources/views/admin/deleteclientsuccess.blade.php @@ -0,0 +1,9 @@ +@extends('master') + +@section('title') +Edit Client « Admin CP +@stop + +@section('content') +

    Successfully deleted the client information.

    +@stop diff --git a/resources/views/admin/deletecontact.blade.php b/resources/views/admin/deletecontact.blade.php new file mode 100644 index 00000000..f855e4fc --- /dev/null +++ b/resources/views/admin/deletecontact.blade.php @@ -0,0 +1,14 @@ +@extends('master') + +@section('title') +Delete Contact? « Admin CP +@stop + +@section('content') +
    + + +
    + +
    +@stop diff --git a/resources/views/admin/deletecontactsuccess.blade.php b/resources/views/admin/deletecontactsuccess.blade.php new file mode 100644 index 00000000..df7f71c7 --- /dev/null +++ b/resources/views/admin/deletecontactsuccess.blade.php @@ -0,0 +1,9 @@ +@extends('master') + +@section('title') +Deleted Contact « Admin CP +@stop + +@section('content') +

    Successfully deleted the contact information.

    +@stop diff --git a/resources/views/admin/deletetoken.blade.php b/resources/views/admin/deletetoken.blade.php new file mode 100644 index 00000000..fbaed92b --- /dev/null +++ b/resources/views/admin/deletetoken.blade.php @@ -0,0 +1,13 @@ +@extends('master') + +@section('title') +Delete Token? « Admin CP +@stop + +@section('content') +
    + +
    + +
    +@stop diff --git a/resources/views/admin/deletetokensuccess.blade.php b/resources/views/admin/deletetokensuccess.blade.php new file mode 100644 index 00000000..4e2709e2 --- /dev/null +++ b/resources/views/admin/deletetokensuccess.blade.php @@ -0,0 +1,9 @@ +@extends('master') + +@section('title') +Token Deleted « Admin CP +@stop + +@section('content') +

    You have successfully deletd the token: {{ $id }}

    +@stop diff --git a/resources/views/admin/editarticle.blade.php b/resources/views/admin/editarticle.blade.php new file mode 100644 index 00000000..3e2ec22f --- /dev/null +++ b/resources/views/admin/editarticle.blade.php @@ -0,0 +1,41 @@ +@extends('master') + +@section('title') +Edit Article « Admin CP +@stop + +@section('content') +
    + + +
    + +
    + +
    + +
    + +
    + +
    + +
    +

    Preview

    +@stop + +@section('scripts') +@parent + + +@stop diff --git a/resources/views/admin/editarticlesuccess.blade.php b/resources/views/admin/editarticlesuccess.blade.php new file mode 100644 index 00000000..39a45e3b --- /dev/null +++ b/resources/views/admin/editarticlesuccess.blade.php @@ -0,0 +1,9 @@ +@extends('master') + +@section('title') +Article Successfully Edited « Admin CP +@stop + +@section('content') +

    Successfully edited article with id: {{ $id }}

    +@stop diff --git a/resources/views/admin/editclient.blade.php b/resources/views/admin/editclient.blade.php new file mode 100644 index 00000000..cb3db738 --- /dev/null +++ b/resources/views/admin/editclient.blade.php @@ -0,0 +1,15 @@ +@extends('master') + +@section('title') +Edit Client « Admin CP +@stop + +@section('content') +

    Edit Client

    +
    +
    +
    +

    + +
    +@stop diff --git a/resources/views/admin/editclientsuccess.blade.php b/resources/views/admin/editclientsuccess.blade.php new file mode 100644 index 00000000..69068bad --- /dev/null +++ b/resources/views/admin/editclientsuccess.blade.php @@ -0,0 +1,9 @@ +@extends('master') + +@section('title') +Edit Client « Admin CP +@stop + +@section('content') +

    Successfully edited the client information.

    +@stop diff --git a/resources/views/admin/editcontact.blade.php b/resources/views/admin/editcontact.blade.php new file mode 100644 index 00000000..100a7055 --- /dev/null +++ b/resources/views/admin/editcontact.blade.php @@ -0,0 +1,20 @@ +@extends('master') + +@section('title') +Edit Contact « Admin CP +@stop + +@section('content') +

    Edit Contact

    +
    + +
    +
    +
    +
    +
    + +
    +

    Or do you want to delete this contact?

    +

    Instead of uploading an image, you can grab from their homepage?

    +@stop \ No newline at end of file diff --git a/resources/views/admin/editcontactsuccess.blade.php b/resources/views/admin/editcontactsuccess.blade.php new file mode 100644 index 00000000..c9712779 --- /dev/null +++ b/resources/views/admin/editcontactsuccess.blade.php @@ -0,0 +1,9 @@ +@extends('master') + +@section('title') +Edit Contact « Admin CP +@stop + +@section('content') +

    Successfully edited the contact information.

    +@stop diff --git a/resources/views/admin/editnote.blade.php b/resources/views/admin/editnote.blade.php new file mode 100644 index 00000000..6bf7e1ee --- /dev/null +++ b/resources/views/admin/editnote.blade.php @@ -0,0 +1,18 @@ +@extends('master') + +@section('title') +Edit Note « Admin CP +@stop + +@section('content') +
    + +
    + Edit Note +
    +
    +
    +
    +
    +
    +@stop diff --git a/resources/views/admin/editnotesuccess.blade.php b/resources/views/admin/editnotesuccess.blade.php new file mode 100644 index 00000000..4a138dd1 --- /dev/null +++ b/resources/views/admin/editnotesuccess.blade.php @@ -0,0 +1,9 @@ +@extends('master') + +@section('title') +Note Successfully Edited « Admin CP +@stop + +@section('content') +

    Successfully edited note with id: {{ $id }}.

    +@stop diff --git a/resources/views/admin/editplace.blade.php b/resources/views/admin/editplace.blade.php new file mode 100644 index 00000000..93e92763 --- /dev/null +++ b/resources/views/admin/editplace.blade.php @@ -0,0 +1,18 @@ +@extends('master') + +@section('title') +Edit Place « Admin CP +@stop + +@section('content') +

    Edit Place

    +
    + +
    +
    +
    +
    +

    + +
    +@stop diff --git a/resources/views/admin/editplacesuccess.blade.php b/resources/views/admin/editplacesuccess.blade.php new file mode 100644 index 00000000..eb4ef9a3 --- /dev/null +++ b/resources/views/admin/editplacesuccess.blade.php @@ -0,0 +1,9 @@ +@extends('master') + +@section('title') +Edit Place « Admin CP +@stop + +@section('content') +

    Successfully edited the place information.

    +@stop diff --git a/resources/views/admin/getavatarsuccess.blade.php b/resources/views/admin/getavatarsuccess.blade.php new file mode 100644 index 00000000..d0b0187d --- /dev/null +++ b/resources/views/admin/getavatarsuccess.blade.php @@ -0,0 +1,10 @@ +@extends('master') + +@section('title') +New Contact Avatar « Admin CP +@stop + +@section('content') +

    Successfully saved the avatar contact

    + +@stop diff --git a/resources/views/admin/listarticles.blade.php b/resources/views/admin/listarticles.blade.php new file mode 100644 index 00000000..04049827 --- /dev/null +++ b/resources/views/admin/listarticles.blade.php @@ -0,0 +1,14 @@ +@extends('master') + +@section('title') +List Articles « Admin CP +@stop + +@section('content') +

    Select article to edit:

    +
      +@foreach($posts as $post) +
    1. {{ $post['title'] }}@if($post['published'] == '0')not published@endif Delete? +@endforeach +
    +@stop diff --git a/resources/views/admin/listclients.blade.php b/resources/views/admin/listclients.blade.php new file mode 100644 index 00000000..ad699eed --- /dev/null +++ b/resources/views/admin/listclients.blade.php @@ -0,0 +1,17 @@ +@extends('master') + +@section('title') +List Clients « Admin CP +@stop + +@section('content') +

    Clients

    +
      +@foreach($clients as $client) +
    • {{ $client['client_url'] }} : {{ $client['client_name'] }} + edit? +
    • +@endforeach +
    +

    Createn a new entry?

    +@stop diff --git a/resources/views/admin/listcontacts.blade.php b/resources/views/admin/listcontacts.blade.php new file mode 100644 index 00000000..86a5e2d2 --- /dev/null +++ b/resources/views/admin/listcontacts.blade.php @@ -0,0 +1,27 @@ +@extends('master') + +@section('title') +List Contacts « Admin CP +@stop + +@section('content') +

    Contacts

    + + + + + + + + +@foreach($contacts as $contact) + + + + + + + +@endforeach +
    Real NameNickHomepageTwitter
    {{ $contact->name }}{{ $contact->nick }}{{ $contact->homepage }}{{ $contact->twitter }}edit
    +@stop \ No newline at end of file diff --git a/resources/views/admin/listnotes.blade.php b/resources/views/admin/listnotes.blade.php new file mode 100644 index 00000000..1bf767e6 --- /dev/null +++ b/resources/views/admin/listnotes.blade.php @@ -0,0 +1,14 @@ +@extends('master') + +@section('title') +List Notes « Admin CP +@stop + +@section('content') +

    Select note to edit:

    +
      +@foreach($notes as $note) +
    1. {{ $note->originalNote }}
    2. +@endforeach +
    +@stop diff --git a/resources/views/admin/listplaces.blade.php b/resources/views/admin/listplaces.blade.php new file mode 100644 index 00000000..a51c0bfb --- /dev/null +++ b/resources/views/admin/listplaces.blade.php @@ -0,0 +1,15 @@ +@extends('master') + +@section('title') +List Places « Admin CP +@stop + +@section('content') +

    Places

    +
      +@foreach($places as $place) +
    • {{ $place['name'] }} edit?
    • +@endforeach +
    +

    Createn a new entry?

    +@stop diff --git a/resources/views/admin/listtokens.blade.php b/resources/views/admin/listtokens.blade.php new file mode 100644 index 00000000..4039f461 --- /dev/null +++ b/resources/views/admin/listtokens.blade.php @@ -0,0 +1,21 @@ +@extends('master') + +@section('title') +List Tokens « Admin CP +@stop + +@section('content') +

    Tokens

    +
      +@foreach($tokens as $token => $data) +
    • {{ $token }} +
        + @foreach($data as $key => $value) +
      • {{ $key }}: '; foreach($value as $scope) { echo "
      • $scope
      • "; } echo '
      '; } else { echo $value; }; ?>
    • + @endforeach +
    + delete? + +@endforeach + +@stop diff --git a/resources/views/admin/newarticle.blade.php b/resources/views/admin/newarticle.blade.php new file mode 100644 index 00000000..a8061147 --- /dev/null +++ b/resources/views/admin/newarticle.blade.php @@ -0,0 +1,49 @@ +@extends('master') + +@section('title') +New Article « Admin CP +@stop + +@section('content') +@if(isset($message))

    {{ $message }}

    @endif +
    + + +
    + +
    + +
    + +
    + +
    + +
    +

    Or you can upload an .md file:

    +
    + +
    +

    Preview

    +@stop + +@section('scripts') +@parent + + + + + + + +@stop diff --git a/resources/views/admin/newarticlesuccess.blade.php b/resources/views/admin/newarticlesuccess.blade.php new file mode 100644 index 00000000..6bc1a0b3 --- /dev/null +++ b/resources/views/admin/newarticlesuccess.blade.php @@ -0,0 +1,9 @@ +@extends('master') + +@section('title') +New Article Success « Admin CP +@stop + +@section('content') +

    Successfully created article with id: {{ $id }}, title: {{ $title }}

    +@stop diff --git a/resources/views/admin/newclient.blade.php b/resources/views/admin/newclient.blade.php new file mode 100644 index 00000000..a7f50405 --- /dev/null +++ b/resources/views/admin/newclient.blade.php @@ -0,0 +1,15 @@ +@extends('master') + +@section('title') +New Client « Admin CP +@stop + +@section('content') +

    New Client

    +
    + +
    +
    + +
    +@stop diff --git a/resources/views/admin/newclientsuccess.blade.php b/resources/views/admin/newclientsuccess.blade.php new file mode 100644 index 00000000..ac23d4fa --- /dev/null +++ b/resources/views/admin/newclientsuccess.blade.php @@ -0,0 +1,9 @@ +@extends('master') + +@section('title') +New Client « Admin CP +@stop + +@section('content') +

    Succesfully created new client info.

    +@stop diff --git a/resources/views/admin/newcontact.blade.php b/resources/views/admin/newcontact.blade.php new file mode 100644 index 00000000..0c38a367 --- /dev/null +++ b/resources/views/admin/newcontact.blade.php @@ -0,0 +1,17 @@ +@extends('master') + +@section('title') +New Contact « Admin CP +@stop + +@section('content') +

    New Contact

    +
    + +
    +
    +
    +
    + +
    +@stop \ No newline at end of file diff --git a/resources/views/admin/newcontactsuccess.blade.php b/resources/views/admin/newcontactsuccess.blade.php new file mode 100644 index 00000000..13fe7532 --- /dev/null +++ b/resources/views/admin/newcontactsuccess.blade.php @@ -0,0 +1,9 @@ +@extends('master') + +@section('title') +New Contact « Admin CP +@stop + +@section('content') +

    Succesfully created new contact entry.

    +@stop diff --git a/resources/views/admin/newnote.blade.php b/resources/views/admin/newnote.blade.php new file mode 100644 index 00000000..9787c822 --- /dev/null +++ b/resources/views/admin/newnote.blade.php @@ -0,0 +1,34 @@ +@extends('master') + +@section('title') +New Note « Admin CP +@stop + +@section('content') +@if (count($errors) > 0) +
    +
      + @foreach ($errors->all() as $error) +
    • {{ $error }}
    • + @endforeach +
    +
    +@endif +@include('templates.new-note-form', [ + 'micropub' => false, + 'action' => '/admin/note/new', + 'id' => 'newnote-admin' +]) +@stop + +@section('scripts') + + + + + + + + + +@stop diff --git a/resources/views/admin/newnotesuccess.blade.php b/resources/views/admin/newnotesuccess.blade.php new file mode 100644 index 00000000..d96fda81 --- /dev/null +++ b/resources/views/admin/newnotesuccess.blade.php @@ -0,0 +1,9 @@ +@extends('master') + +@section('title') +New Note Success « Admin CP +@stop + +@section('content') +

    Successfully created note with id: {{ $id }}. {{ $shorturl }}

    +@stop diff --git a/resources/views/admin/newplace.blade.php b/resources/views/admin/newplace.blade.php new file mode 100644 index 00000000..c4f9b8d4 --- /dev/null +++ b/resources/views/admin/newplace.blade.php @@ -0,0 +1,24 @@ +@extends('master') + +@section('title') +New Place « Admin CP +@stop + +@section('content') +

    New Place

    +
    + +
    +
    +
    +
    + +

    Location

    + +
    +@stop + +@section('scripts') + + +@stop diff --git a/resources/views/admin/newplacesuccess.blade.php b/resources/views/admin/newplacesuccess.blade.php new file mode 100644 index 00000000..787aac8c --- /dev/null +++ b/resources/views/admin/newplacesuccess.blade.php @@ -0,0 +1,9 @@ +@extends('master') + +@section('title') +New Place « Admin CP +@stop + +@section('content') +

    Succesfully created new place info.

    +@stop diff --git a/resources/views/admin/welcome.blade.php b/resources/views/admin/welcome.blade.php new file mode 100644 index 00000000..c60d66db --- /dev/null +++ b/resources/views/admin/welcome.blade.php @@ -0,0 +1,24 @@ +@extends('master') + +@section('title') +Admin CP +@stop + +@section('content') +

    Hello {{ $name }}!

    + +

    Articles

    +

    You can either create new blog posts, or edit them.

    + +

    Notes

    +

    You can either create new notes, or edit them.

    + +

    Tokens

    +

    See all issued tokens.

    + +

    Contacts

    +

    You can either create new contacts, or edit them.

    + +

    Places

    +

    You can either create new places, or edit them.

    +@stop diff --git a/resources/views/allnotes.blade.php b/resources/views/allnotes.blade.php new file mode 100644 index 00000000..eb41df56 --- /dev/null +++ b/resources/views/allnotes.blade.php @@ -0,0 +1,31 @@ +@extends('master') + +@section('title') +Notes « Jonny Barnes +@stop + +@section('content') +
    + + + @foreach ($notes as $note) +
    + @include('templates.note', ['note' => $note]) +
    + @endforeach +
    +{!! $notes->render() !!} +@stop + +@section('scripts') + + + + + + + + + +@stop diff --git a/resources/views/allplaces.blade.php b/resources/views/allplaces.blade.php new file mode 100644 index 00000000..94445d72 --- /dev/null +++ b/resources/views/allplaces.blade.php @@ -0,0 +1,13 @@ +@extends('master') + +@section('title') +Places « Jonny Barnes +@stop + +@section('content') + +@stop diff --git a/resources/views/contact-template.blade.php b/resources/views/contact-template.blade.php new file mode 100644 index 00000000..d6dae0af --- /dev/null +++ b/resources/views/contact-template.blade.php @@ -0,0 +1,10 @@ +
    +
    + {{ $contact->name }} {{ '@' . $contact->nick }} + +
    + +
    \ No newline at end of file diff --git a/resources/views/contact.blade.php b/resources/views/contact.blade.php new file mode 100644 index 00000000..1cecd3d1 --- /dev/null +++ b/resources/views/contact.blade.php @@ -0,0 +1,9 @@ +@extends('master') + +@section('title') +Contacts « Jonny Barnes +@stop + +@section('content') +@include('contact-template', array('contact' => $contact)) +@stop \ No newline at end of file diff --git a/resources/views/contacts.blade.php b/resources/views/contacts.blade.php new file mode 100644 index 00000000..b53b34bf --- /dev/null +++ b/resources/views/contacts.blade.php @@ -0,0 +1,11 @@ +@extends('master') + +@section('title') +Contacts « Jonny Barnes +@stop + +@section('content') +@foreach($contacts as $contact) +@include('contact-template', array('contact' => $contact)) +@endforeach +@stop \ No newline at end of file diff --git a/resources/views/errors/503.blade.php b/resources/views/errors/503.blade.php new file mode 100644 index 00000000..4a415059 --- /dev/null +++ b/resources/views/errors/503.blade.php @@ -0,0 +1,47 @@ + + + + Be right back. + + + + + + +
    +
    +
    Be right back.
    +
    +
    + + diff --git a/resources/views/homepage.blade.php b/resources/views/homepage.blade.php new file mode 100644 index 00000000..a889caa3 --- /dev/null +++ b/resources/views/homepage.blade.php @@ -0,0 +1,29 @@ +@extends('master') + +@section('title') +Jonny Barnes +@stop + +@section('content') +
    +

    Hello

    +

    My name is Jonny Barnes, and I’m from Manchester, UK. I love everything web-related and this is a little place on the web I call my own. My aim now is to try and adhere to the IndieWeb principles and thus own my data.

    +

    My aim for this homepage is to turn it into a stream of my latest notes and articles I’ve written. Then maybe pull data from places like last.fm. Talking of which:

    + +

    Me Around the Web

    +

    Obviously there’s this website, which is my main online identity.

    +

    I am active to varying degrees on several silos: +

    +

    My usual online handle is jonnybarnes for other services, though if they’re not listed above then I don’t actively use the service. My usual profile pic. I also have a PGP key, with fingerprint.

    + +

    Though of course all this activity should eventually “flow” through this website if it is to truely be my online identity.

    +
    +@stop diff --git a/resources/views/login.blade.php b/resources/views/login.blade.php new file mode 100644 index 00000000..a6f9d86c --- /dev/null +++ b/resources/views/login.blade.php @@ -0,0 +1,12 @@ +@extends('master') +@section('title')Login @stop + +@section('content') +

    Login

    +
    + + + + +
    +@stop diff --git a/resources/views/master.blade.php b/resources/views/master.blade.php new file mode 100644 index 00000000..c17613dc --- /dev/null +++ b/resources/views/master.blade.php @@ -0,0 +1,58 @@ + + + + + @if (App::environment() == 'local'){!! "[testing] -"!!}@endif @yield('title') + + + + + + + + + + + + + +
    + + +
    + +
    +@yield('content') +
    + + + @section('scripts') + + @show + + {{-- The piwik code that should only be shown in production --}} + @if (env('PIWIK_URL') !== null) + + + + + @endif + + diff --git a/resources/views/micropubnewnotepage.blade.php b/resources/views/micropubnewnotepage.blade.php new file mode 100644 index 00000000..4aa5bd38 --- /dev/null +++ b/resources/views/micropubnewnotepage.blade.php @@ -0,0 +1,45 @@ +@extends('master') + +@section('title') +New Note « Jonny Barnes +@stop + +@section('content') +

    This is my UI for posting new notes, hopefully you’ll soon be able to use this if your site supports the micropub API.

    +@if($errors->endpoint->first() != '') +

    {{ $errors->endpoint->first() }}

    +@endif +@if($errors->indieauth->first() != '') +

    {{ $errors->indieauth->first() }}

    +@endif +@if($url === null) +
    + +
    + IndieAuth + + +
    +
    +@else +

    You are authenticated as {{ $url }}, log out.

    +@endif + @include('templates.new-note-form', [ + 'micropub' => true, + 'action' => '/notes/new', + 'id' => 'newnote' + ]) +@stop + +@section('scripts') + + + + + + + + + + +@stop diff --git a/resources/views/mini-hcard-template.blade.php b/resources/views/mini-hcard-template.blade.php new file mode 100644 index 00000000..c2ab1eba --- /dev/null +++ b/resources/views/mini-hcard-template.blade.php @@ -0,0 +1,4 @@ + + + {!! $contact->name !!} + diff --git a/resources/views/multipost.blade.php b/resources/views/multipost.blade.php new file mode 100644 index 00000000..59bab313 --- /dev/null +++ b/resources/views/multipost.blade.php @@ -0,0 +1,30 @@ +@extends('master') + +@section('title') +Articles « Jonny Barnes +@stop + +@section('content') +@if (count($data) == 0) +

    No articles exist for this time.

    +@endif +@foreach ($data as $article) +@if ($article['url'] != '')
    @else +@endforeach +{!! $data->render() !!} +@stop + +@section('scripts') + + +@stop diff --git a/resources/views/projects.blade.php b/resources/views/projects.blade.php new file mode 100644 index 00000000..a02f66d5 --- /dev/null +++ b/resources/views/projects.blade.php @@ -0,0 +1,14 @@ +@extends('master') +@section('title')Jonny Barnes’ Projects @stop + +@section('content') +
    +

    Projects

    +

    Shaaaaaaaaaaaaa.com

    +

    I’m collaborating on a project with Eric Mill (@konklone) to help people test their HTTPS certificates for weak signature algorithms. SHA-1 is the current standard, but is too weak. People should use a form of SHA-2.

    +

    IndieWeb tools

    +

    This library consists of various useful tools for running an IndieWeb aware site.

    +

    Webmentions Parser

    +

    A tool to parse incoming webmentions to a site, including determining the author of the source webmention.

    +
    +@stop diff --git a/resources/views/rss.blade.php b/resources/views/rss.blade.php new file mode 100644 index 00000000..8ec8a0a9 --- /dev/null +++ b/resources/views/rss.blade.php @@ -0,0 +1,22 @@ + + + + Jonny Barnes.uk + + An RSS feed of the blog posts found on jonnybarnes.uk + https://jonnybarnes.uk + {{ $buildDate }} + 1800 + + @foreach($articles as $article) + + {{ strip_tags($article->title) }} + main }}@if($article->url)

    Permalink

    @endif]]>
    + @if($article->url != ''){{ $article->url }}@else{{ config('app.url') }}{{ $article->link }}@endif + {{ config('app.url') }}{{ $article->link }} + {{ $article->pubdate }} +
    + @endforeach + +
    +
    diff --git a/resources/views/singlenote.blade.php b/resources/views/singlenote.blade.php new file mode 100644 index 00000000..40b0101c --- /dev/null +++ b/resources/views/singlenote.blade.php @@ -0,0 +1,43 @@ +@extends('master') + +@section('title') +{{ strip_tags($note->note) }} « Notes « Jonny Barnes +@stop + +@section('content') +
    + @include('templates.note', ['note' => $note]) +@foreach($replies as $reply) +
    + + {{ $reply['name'] }} + said at {{ $reply['date'] }} +
    + {!! $reply['reply'] !!} +
    +
    +@endforeach +
    +@if(count($likes) > 0)

    Likes

    @endif +@foreach($likes as $like) + +@endforeach +@if(count($reposts) > 0)

    Reposts

    @endif +@foreach($reposts as $repost) +

    + {{ $repost['name'] }} + reposted this at {{ $repost['date'] }}.

    +@endforeach +@stop + +@section('scripts') + + + + + + + + + +@stop diff --git a/resources/views/singleplace.blade.php b/resources/views/singleplace.blade.php new file mode 100644 index 00000000..eb5e3ce6 --- /dev/null +++ b/resources/views/singleplace.blade.php @@ -0,0 +1,21 @@ +@extends('master') + +@section('title') +{{ $place->name }} « Places « Jonny Barnes +@stop + +@section('content') +
    +

    {{ $place->name }}

    +

    {{ $place->description or 'No description'}}

    +
    +

    Latitude: {{ $place->latitude }}, longitude: {{ $place->longitude }}

    +
    +@stop + +@section('scripts') + + + + +@stop diff --git a/resources/views/singlepost.blade.php b/resources/views/singlepost.blade.php new file mode 100644 index 00000000..8b42a7fb --- /dev/null +++ b/resources/views/singlepost.blade.php @@ -0,0 +1,24 @@ +@extends('master') + +@section('title') +{{ strip_tags($article->title) }} Jonny Barnes +@stop + +@section('content') +@if($article->url != '')
    @else +@stop + +@section('scripts') + + +@stop diff --git a/resources/views/taggednotes.blade.php b/resources/views/taggednotes.blade.php new file mode 100644 index 00000000..53a9bb04 --- /dev/null +++ b/resources/views/taggednotes.blade.php @@ -0,0 +1,13 @@ +@extends('master') + +@section('title') +Notes Jonny Barnes +@stop + +@section('content') +

    Notes tagged with {{ $tag }}

    +@foreach ($notes as $note) +
    {!! $note->note !!} +{{ $note->human_time }}
    +@endforeach +@stop \ No newline at end of file diff --git a/resources/views/templates/new-note-form.blade.php b/resources/views/templates/new-note-form.blade.php new file mode 100644 index 00000000..e0c55ca2 --- /dev/null +++ b/resources/views/templates/new-note-form.blade.php @@ -0,0 +1,16 @@ +
    + +
    + New Note + + +
    +@if ($micropub === true) + @if($syndication)@endif + Refresh Syndication Targets
    +@endif + + + +
    +
    diff --git a/resources/views/templates/note.blade.php b/resources/views/templates/note.blade.php new file mode 100644 index 00000000..76f8eeb4 --- /dev/null +++ b/resources/views/templates/note.blade.php @@ -0,0 +1,26 @@ +@if ($note->twitter) + {!! $note->twitter->html !!} +@elseif ($note->in_reply_to) +
    + In reply to {{ $note->in_reply_to }} +
    +@endif +
    +
    + {!! $note->note !!} + @if(count($note->photoURLs) > 0) + @foreach($note->photoURLs as $photoURL) + + @endforeach + @endif +
    + +
    diff --git a/resources/views/templates/social-links.blade.php b/resources/views/templates/social-links.blade.php new file mode 100644 index 00000000..9f31a2f8 --- /dev/null +++ b/resources/views/templates/social-links.blade.php @@ -0,0 +1,31 @@ + diff --git a/resources/views/vendor/.gitkeep b/resources/views/vendor/.gitkeep new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/resources/views/vendor/.gitkeep @@ -0,0 +1 @@ + diff --git a/resources/views/webmention-endpoint.blade.php b/resources/views/webmention-endpoint.blade.php new file mode 100644 index 00000000..2422bc87 --- /dev/null +++ b/resources/views/webmention-endpoint.blade.php @@ -0,0 +1,9 @@ +@extends('master') + +@section('title') +WebMentions « Jonny Barnes +@stop + +@section('content') +

    My WebMention endpoint.

    +@stop diff --git a/resources/views/welcome.blade.php b/resources/views/welcome.blade.php new file mode 100644 index 00000000..87710ace --- /dev/null +++ b/resources/views/welcome.blade.php @@ -0,0 +1,45 @@ + + + + Laravel + + + + + + +
    +
    +
    Laravel 5
    +
    +
    + + diff --git a/server.php b/server.php new file mode 100644 index 00000000..f65c7c44 --- /dev/null +++ b/server.php @@ -0,0 +1,21 @@ + + */ + +$uri = urldecode( + parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH) +); + +// This file allows us to emulate Apache's "mod_rewrite" functionality from the +// built-in PHP web server. This provides a convenient way to test a Laravel +// application without having installed a "real" web server software here. +if ($uri !== '/' && file_exists(__DIR__.'/public'.$uri)) { + return false; +} + +require_once __DIR__.'/public/index.php'; diff --git a/storage/HTML/.gitignore b/storage/HTML/.gitignore new file mode 100644 index 00000000..bf27f311 --- /dev/null +++ b/storage/HTML/.gitignore @@ -0,0 +1,3 @@ +* +!.gitignore +!.gitkeep diff --git a/storage/HTML/.gitkeep b/storage/HTML/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/storage/HTMLPurifier/.gitignore b/storage/HTMLPurifier/.gitignore new file mode 100644 index 00000000..d6b7ef32 --- /dev/null +++ b/storage/HTMLPurifier/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore diff --git a/storage/app/.gitignore b/storage/app/.gitignore new file mode 100644 index 00000000..8f4803c0 --- /dev/null +++ b/storage/app/.gitignore @@ -0,0 +1,3 @@ +* +!public/ +!.gitignore diff --git a/storage/app/public/.gitignore b/storage/app/public/.gitignore new file mode 100644 index 00000000..d6b7ef32 --- /dev/null +++ b/storage/app/public/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore diff --git a/storage/debugbar/.gitignore b/storage/debugbar/.gitignore new file mode 100644 index 00000000..c96a04f0 --- /dev/null +++ b/storage/debugbar/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore \ No newline at end of file diff --git a/storage/framework/.gitignore b/storage/framework/.gitignore new file mode 100644 index 00000000..b02b700f --- /dev/null +++ b/storage/framework/.gitignore @@ -0,0 +1,8 @@ +config.php +routes.php +schedule-* +compiled.php +services.json +events.scanned.php +routes.scanned.php +down diff --git a/storage/framework/cache/.gitignore b/storage/framework/cache/.gitignore new file mode 100644 index 00000000..d6b7ef32 --- /dev/null +++ b/storage/framework/cache/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore diff --git a/storage/framework/sessions/.gitignore b/storage/framework/sessions/.gitignore new file mode 100644 index 00000000..d6b7ef32 --- /dev/null +++ b/storage/framework/sessions/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore diff --git a/storage/framework/views/.gitignore b/storage/framework/views/.gitignore new file mode 100644 index 00000000..d6b7ef32 --- /dev/null +++ b/storage/framework/views/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore diff --git a/storage/logs/.gitignore b/storage/logs/.gitignore new file mode 100644 index 00000000..d6b7ef32 --- /dev/null +++ b/storage/logs/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore diff --git a/storage/media-tmp/.gitkeep b/storage/media-tmp/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/storage/medialibrary/.gitignore b/storage/medialibrary/.gitignore new file mode 100644 index 00000000..c96a04f0 --- /dev/null +++ b/storage/medialibrary/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore \ No newline at end of file diff --git a/storage/tokens/.gitignore b/storage/tokens/.gitignore new file mode 100644 index 00000000..bf27f311 --- /dev/null +++ b/storage/tokens/.gitignore @@ -0,0 +1,3 @@ +* +!.gitignore +!.gitkeep diff --git a/storage/tokens/.gitkeep b/storage/tokens/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/tests/ArticlesTest.php b/tests/ArticlesTest.php new file mode 100644 index 00000000..d39ce868 --- /dev/null +++ b/tests/ArticlesTest.php @@ -0,0 +1,78 @@ +appurl = config('app.url'); + } + + /** + * Test the `/blog` page returns the article, this + * means the database is being hit. + * + * @return void + */ + public function testArticlesPage() + { + $this->visit($this->appurl . '/blog') + ->see('My New Blog'); + } + + /** + * Test the `/blog/{year}` page returns the article, this + * means the database is being hit. + * + * @return void + */ + public function testArticlesYearPage() + { + $this->visit($this->appurl . '/blog/2016') + ->see('My New Blog'); + } + + /** + * Test the `/blog/{year}/{month}` page returns the article, + * this means the database is being hit. + * + * @return void + */ + public function testArticlesMonthPage() + { + $this->visit($this->appurl . '/blog/2016/01') + ->see('My New Blog'); + } + + /** + * Test a single article page. + * + * @return void + */ + public function testSingleArticlePage() + { + $this->visit($this->appurl . '/blog/2016/01/my-new-blog') + ->see('My New Blog'); + } + + /** + * Test the RSS feed. + * + * @return void + */ + public function testRSSFeed() + { + $response = $this->call('GET', $this->appurl . '/feed'); + + $this->assertEquals('application/rss+xml', $response->headers->get('Content-Type')); + } +} diff --git a/tests/ContactsTest.php b/tests/ContactsTest.php new file mode 100644 index 00000000..16318a6b --- /dev/null +++ b/tests/ContactsTest.php @@ -0,0 +1,52 @@ +appurl = config('app.url'); + } + + /** + * Test the `/contacts` page and see if response is OK. + * + * @return void + */ + public function testContactsPage() + { + $this->visit($this->appurl . '/contacts') + ->assertResponseOK(); + } + + /** + * Test an individual contact page with default profile image. + * + * @return void + */ + public function testContactPageWithDefaultPic() + { + $this->visit($this->appurl . '/contacts/tantek') + ->see(''); + } + + /** + * Test an individual contact page with a specific profile image. + * + * @return void + */ + public function testContactPageWithSpecificPic() + { + $this->visit($this->appurl . '/contacts/aaron') + ->see(''); + } +} diff --git a/tests/ExampleTest.php b/tests/ExampleTest.php new file mode 100644 index 00000000..dee80424 --- /dev/null +++ b/tests/ExampleTest.php @@ -0,0 +1,19 @@ +visit(config('app.url') . '/') + ->see('Jonny Barnes'); + } +} diff --git a/tests/IndieAuthTest.php b/tests/IndieAuthTest.php new file mode 100644 index 00000000..75c15554 --- /dev/null +++ b/tests/IndieAuthTest.php @@ -0,0 +1,79 @@ +appurl = config('app.url'); + } + + /** + * Test the getAuthorizationEndpoint calls the correct service methods, + * though these methods are actually mocked. + * + * @return void + */ + public function testIndieAuthServiceDiscoversEndpoint() + { + $service = new \App\Services\IndieAuthService(); + $client = new \IndieAuth\Client(); + $result = $service->getAuthorizationEndpoint($this->appurl, $client); + $this->assertSame('https://indieauth.com/auth', $result); + } + + /** + * Test that the Service build the correct redirect URL. + * + * @return void + */ + public function testIndieAuthServiceBuildRedirectURL() + { + $client = new \IndieAuth\Client(); + $service = new \App\Services\IndieAuthService(); + $result = $service->buildAuthorizationURL( + 'https://indieauth.com/auth', + $this->appurl, + $client + ); + $this->assertSame( + 'https://indieauth.com/auth?me=', + substr($result, 0, 30) + ); + } + + /** + * Test the `beginauth` method redirects to the client on error. + * + * @return void + */ + public function testIndieAuthControllerBeginAuthRedirectsToClientOnFail() + { + $response = $this->call('GET', $this->appurl . '/beginauth', ['me' => 'http://example.org']); + $this->assertSame($this->appurl . '/notes/new', $response->headers->get('Location')); + } + + /** + * Now we test the `beginauth` method as a whole. + * + * @return void + */ + public function testIndieAuthControllerBeginAuthRedirectsToEndpoint() + { + $response = $this->call('GET', $this->appurl . '/beginauth', ['me' => $this->appurl]); + $this->assertSame( + 'https://indieauth.com/auth?me=', + substr($response->headers->get('Location'), 0, 30) + ); + $response = null; + } +} diff --git a/tests/MicropubClientTest.php b/tests/MicropubClientTest.php new file mode 100644 index 00000000..f94006db --- /dev/null +++ b/tests/MicropubClientTest.php @@ -0,0 +1,73 @@ +appurl = config('app.url'); + } + + /** + * Test the client gets shown for an unauthorised request. + * + * @return void + */ + public function testClientPageUnauthorised() + { + $this->visit($this->appurl . '/notes/new') + ->see('IndieAuth'); + } + + public function testClientPageRecentAuth() + { + $this->withSession([ + 'me' => $this->appurl, + 'syndication' => 'mp-syndicate-to=twitter.com%2Fjbl5', + ])->visit($this->appurl . '/notes/new') + ->see($this->appurl) + ->see('twitter.com/jbl5'); + } + + /** + * This currently creates a new note that stays in the database. + */ + public function testClientCreatesNewNote() + { + $faker = \Faker\Factory::create(); + $note = 'Fake note from PHPUnit: ' . $faker->text; + $this->withSession([ + 'me' => $this->appurl, + 'token' => $this->getToken() + ])->visit($this->appurl . '/notes/new') + ->type($note, 'content') + ->press('Submit'); + $this->seeInDatabase('notes', ['note' => $note]); + + } + + private function getToken() + { + $signer = new Sha256(); + $token = (new Builder()) + ->set('client_id', 'https://quill.p3k.io') + ->set('me', $this->appurl) + ->set('scope', 'post') + ->set('issued_at', time()) + ->sign($signer, env('APP_KEY')) + ->getToken(); + + return $token; + } +} diff --git a/tests/MicropubTest.php b/tests/MicropubTest.php new file mode 100644 index 00000000..1cac5657 --- /dev/null +++ b/tests/MicropubTest.php @@ -0,0 +1,112 @@ +appurl = config('app.url'); + } + + public function testMicropubRequestWithoutToken() + { + $this->call('GET', $this->appurl . '/api/post'); + $this->assertResponseStatus(400); + $this->see('No OAuth token sent with request.'); + } + + public function testMicropubRequestWithoutValidToken() + { + $this->call('GET', $this->appurl . '/api/post', [], [], [], ['HTTP_Authorization' => 'Bearer abc123']); + $this->assertResponseStatus(400); + $this->see('Invalid token'); + } + + public function testMicropubRequestWithValidToken() + { + $this->call('GET', $this->appurl . '/api/post', [], [], [], ['HTTP_Authorization' => 'Bearer ' . $this->getToken()]); + $this->see('me=https%3A%2F%2Fjbl5.dev'); + } + + public function testMicropubRequestForSyndication() + { + $this->call('GET', $this->appurl . '/api/post', ['q' => 'syndicate-to'], [], [], ['HTTP_Authorization' => 'Bearer ' . $this->getToken()]); + $this->see('twitter.com%2Fjonnybarnes'); + } + + public function testMicropubRequestForNearbyPlacesThatExist() + { + $this->call('GET', $this->appurl . '/api/post', ['q' => 'geo:53.5,-2.38'], [], [], ['HTTP_Authorization' => 'Bearer ' . $this->getToken()]); + $this->see('the-bridgewater-pub'); + } + + public function testMicropubRequestForNearbyPlacesThatDoNotExist() + { + $this->call('GET', $this->appurl . '/api/post', ['q' => 'geo:1.23,4.56'], [], [], ['HTTP_Authorization' => 'Bearer ' . $this->getToken()]); + $this->see('[]'); + } + + public function testMicropubRequestCreateNewNote() + { + $faker = \Faker\Factory::create(); + $note = $faker->text; + $this->call( + 'POST', + $this->appurl . '/api/post', + [ + 'h' => 'entry', + 'content' => $note + ], + [], + [], + ['HTTP_Authorization' => 'Bearer ' . $this->getToken()] + ); + $this->seeInDatabase('notes', ['note' => $note]); + } + + public function testMicropubRequestCreateNewPlace() + { + $faker = \Faker\Factory::create(); + $note = $faker->text; + $this->call( + 'POST', + $this->appurl . '/api/post', + [ + 'h' => 'card', + 'name' => 'The Barton Arms', + 'geo' => 'geo:53.4974,-2.3768' + ], + [], + [], + ['HTTP_Authorization' => 'Bearer ' . $this->getToken()] + ); + $this->seeInDatabase('places', ['slug' => 'the-barton-arms']); + } + + private function getToken() + { + $signer = new Sha256(); + $token = (new Builder()) + ->set('client_id', 'https://quill.p3k.io') + ->set('me', 'https://jbl5.dev') + ->set('scope', 'post') + ->set('issued_at', time()) + ->sign($signer, env('APP_KEY')) + ->getToken(); + + return $token; + } +} diff --git a/tests/NotesTest.php b/tests/NotesTest.php new file mode 100644 index 00000000..233704e3 --- /dev/null +++ b/tests/NotesTest.php @@ -0,0 +1,179 @@ +appurl = config('app.url'); + $this->notesController = new \App\Http\Controllers\NotesController(); + } + + /** + * Test the `/notes` page returns 200, this should + * mean the database is being hit. + * + * @return void + */ + public function testNotesPage() + { + $this->visit($this->appurl . '/notes') + ->assertResponseOk(); + } + + /** + * Test a specific note so that `singleNote()` get called. + * + * @return void + */ + public function testSpecificNote() + { + $this->visit($this->appurl . '/notes/B') + ->see('#beer'); + } + + /** + * Test that `/note/{decID}` redirects to `/notes/{nb60id}`. + * + * @return void + */ + public function testDecIDRedirect() + { + $this->get($this->appurl . '/note/11') + ->assertRedirectedTo(config('app.url') . '/notes/B'); + } + + /** + * Visit the tagged page and see text from the note. + * + * @return void + */ + public function testTaggedNotesPage() + { + $this->visit($this->appurl . '/notes/tagged/beer') + ->see('at the local.'); + } + + /** + * Look for a default image in the contact’s h-card. + * + * @return void + */ + public function testDefaultImageUsed() + { + $this->visit($this->appurl . '/notes/C') + ->see(''); + } + + /** + * Look for a specific profile image in the contact’s h-card. + * + * @return void + */ + public function testProfileImageUsed() + { + $this->visit($this->appurl . '/notes/D') + ->see(''); + } + + /** + * Look for twitter URL when there’s no associated contact. + * + * @return void + */ + public function testTwitterLinkCreatedWhenNoContactFound() + { + $this->visit($this->appurl . '/notes/E') + ->see('@bob'); + } + + /** + * Test hashtag linking. + * + * @return void + */ + public function testHashtags() + { + $this->visit($this->appurl . '/notes/B') + ->see(''); + } + + /** + * Look for the client name after the note. + * + * @return void + */ + public function testClientNameDisplayed() + { + $this->visit($this->appurl . '/notes/D') + ->see('JBL5'); + } + + /** + * Look for the client URL after the note. + * + * @return void + */ + public function testClientURLDisplayed() + { + $this->visit($this->appurl . '/notes/E') + ->see('quill.p3k.io'); + } + + /** + * Test the bridgy url shim method. + * + * @return void + */ + public function testBridgy() + { + $url = 'https://brid-gy.appspot.com/comment/twitter/jonnybarnes/497778866816299008/497781260937203712'; + $expected = 'https://twitter.com/_/status/497781260937203712'; + $this->assertEquals($expected, $this->notesController->bridgyReply($url)); + } + + /** + * Test a correct profile link is formed from a generic URL. + * + * @return void + */ + public function testCreatePhotoLinkWithGenericURL() + { + $homepage = 'https://example.org'; + $expected = '/assets/profile-images/example.org/image'; + $this->assertEquals($expected, $this->notesController->createPhotoLink($homepage)); + } + + /** + * Test a correct profile link is formed from a twitter URL. + * + * @return void + */ + public function testCreatePhotoLinkWithTwitterProfileImageURL() + { + $twitterProfileImage = 'http://pbs.twimg.com/1234'; + $expected = 'https://pbs.twimg.com/1234'; + $this->assertEquals($expected, $this->notesController->createPhotoLink($twitterProfileImage)); + } + + /** + * Test `null` is returned for a twitter profile. + * + * @return void + */ + public function testCreatePhotoLinkWithTwitterURL() + { + $twitterURL = 'https://twitter.com/example'; + $this->assertNull($this->notesController->createPhotoLink($twitterURL)); + } +} diff --git a/tests/PlacesTest.php b/tests/PlacesTest.php new file mode 100644 index 00000000..b81f4ca1 --- /dev/null +++ b/tests/PlacesTest.php @@ -0,0 +1,52 @@ +appurl = config('app.url'); + } + + /** + * Test the `/places` page for OK response. + * + * @return void + */ + public function testPlacesPage() + { + $this->visit($this->appurl . '/places') + ->assertResponseOK(); + } + + /** + * Test a specific place. + * + * @return void + */ + public function testSinglePlace() + { + $this->visit($this->appurl . '/places/the-bridgewater-pub') + ->see('The Bridgewater Pub'); + } + + /** + * Test the nearby method returns a collection. + * + * @return void + */ + public function testNearbyMethod() + { + $nearby = \App\Place::near(53.5, -2.38, 1000); + $this->assertEquals('the-bridgewater-pub', $nearby[0]->slug); + } +} diff --git a/tests/TestCase.php b/tests/TestCase.php new file mode 100644 index 00000000..8578b17e --- /dev/null +++ b/tests/TestCase.php @@ -0,0 +1,25 @@ +make(Illuminate\Contracts\Console\Kernel::class)->bootstrap(); + + return $app; + } +} diff --git a/tests/TokenServiceTest.php b/tests/TokenServiceTest.php new file mode 100644 index 00000000..e3aa00a2 --- /dev/null +++ b/tests/TokenServiceTest.php @@ -0,0 +1,43 @@ +appurl = config('app.url'); + $this->tokenService = new \App\Services\TokenService(); + } + + /** + * Given the token is dependent on a random nonce, the time of creation and + * the APP_KEY, to test, we shall create a token, and then verify it. + * + * @return void + */ + public function testTokenCreationAndValidation() + { + $data = [ + 'me' => 'https://example.org', + 'client_id' => 'https://quill.p3k.io', + 'scope' => 'post' + ]; + $token = $this->tokenService->getNewToken($data); + $valid = $this->tokenService->validateToken($token); + $validData = [ + 'me' => $valid->getClaim('me'), + 'client_id' => $valid->getClaim('client_id'), + 'scope' => $valid->getClaim('scope') + ]; + $this->assertSame($data, $validData); + } +} diff --git a/tests/aaron.png b/tests/aaron.png new file mode 100644 index 00000000..28923438 Binary files /dev/null and b/tests/aaron.png differ