Merge branch 'release/0.5.17'

This commit is contained in:
Jonny Barnes 2017-06-22 17:45:57 +01:00
commit aa126d728d
44 changed files with 4879 additions and 4500 deletions

View file

@ -14,6 +14,7 @@ addons:
packages: packages:
- nginx - nginx
- realpath - realpath
- postgresql-9.6-postgis-2.3
artifacts: artifacts:
s3_region: "eu-west-1" s3_region: "eu-west-1"
paths: paths:

View file

@ -62,29 +62,29 @@ class MicropubController extends Controller
// Log the request // Log the request
Log::debug($request); Log::debug($request);
if ($tokenData->hasClaim('scope')) { if ($tokenData->hasClaim('scope')) {
if (($request->input('h') == 'entry') || ($request->input('type')[0] == 'h-entry')) { if (($request->input('h') == 'entry') || ($request->input('type.0') == 'h-entry')) {
if (stristr($tokenData->getClaim('scope'), 'create') === false) { if (stristr($tokenData->getClaim('scope'), 'create') === false) {
return $this->returnInsufficientScopeResponse(); return $this->returnInsufficientScopeResponse();
} }
$data = []; $data = [];
$data['client-id'] = $tokenData->getClaim('client_id'); $data['client-id'] = $tokenData->getClaim('client_id');
if ($request->header('Content-Type') == 'application/json') { if ($request->header('Content-Type') == 'application/json') {
if (is_string($request->input('properties.content')[0])) { if (is_string($request->input('properties.content.0'))) {
$data['content'] = $request->input('properties.content')[0]; //plaintext content $data['content'] = $request->input('properties.content.0'); //plaintext content
} }
if (is_array($request->input('properties.content')[0]) if (is_array($request->input('properties.content.0'))
&& array_key_exists('html', $request->input('properties.content')[0]) && array_key_exists('html', $request->input('properties.content.0'))
) { ) {
$data['content'] = $request->input('properties.content')[0]['html']; $data['content'] = $request->input('properties.content.0.html');
} }
$data['in-reply-to'] = $request->input('properties.in-reply-to')[0]; $data['in-reply-to'] = $request->input('properties.in-reply-to.0');
// check location is geo: string // check location is geo: string
if (is_string($request->input('properties.location.0'))) { if (is_string($request->input('properties.location.0'))) {
$data['location'] = $request->input('properties.location.0'); $data['location'] = $request->input('properties.location.0');
} }
// check location is h-card // check location is h-card
if (is_array($request->input('properties.location.0'))) { if (is_array($request->input('properties.location.0'))) {
if ($request->input('properties.location.0.type' === 'h-card')) { if ($request->input('properties.location.0.type.0' === 'h-card')) {
try { try {
$place = $this->placeService->createPlaceFromCheckin($request->input('properties.location.0')); $place = $this->placeService->createPlaceFromCheckin($request->input('properties.location.0'));
$data['checkin'] = $place->longurl; $data['checkin'] = $place->longurl;
@ -93,7 +93,7 @@ class MicropubController extends Controller
} }
} }
} }
$data['published'] = $request->input('properties.published')[0]; $data['published'] = $request->input('properties.published.0');
//create checkin place //create checkin place
if (array_key_exists('checkin', $request->input('properties'))) { if (array_key_exists('checkin', $request->input('properties'))) {
$data['swarm-url'] = $request->input('properties.syndication.0'); $data['swarm-url'] = $request->input('properties.syndication.0');
@ -374,7 +374,7 @@ class MicropubController extends Controller
//check post scope //check post scope
if ($tokenData->hasClaim('scope')) { if ($tokenData->hasClaim('scope')) {
if (stristr($token->getClaim('scope'), 'post') === false) { if (stristr($tokenData->getClaim('scope'), 'create') === false) {
return $this->returnInsufficientScopeResponse(); return $this->returnInsufficientScopeResponse();
} }
//check media valid //check media valid

View file

@ -2,16 +2,9 @@
namespace App\Http\Controllers; namespace App\Http\Controllers;
use Twitter; use App\Note;
use HTMLPurifier;
use App\{Note, Tag};
use GuzzleHttp\Client;
use HTMLPurifier_Config;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Jonnybarnes\IndieWeb\Numbers; use Jonnybarnes\IndieWeb\Numbers;
use Illuminate\Filesystem\Filesystem;
use Illuminate\Support\Facades\Cache;
use Jonnybarnes\WebmentionsParser\Authorship;
// Need to sort out Twitter and webmentions! // Need to sort out Twitter and webmentions!
@ -25,41 +18,13 @@ class NotesController extends Controller
*/ */
public function index(Request $request) public function index(Request $request)
{ {
$notes = Note::orderBy('id', 'desc')->with('webmentions', 'place', 'media')->paginate(10); $notes = Note::orderBy('id', 'desc')
foreach ($notes as $note) { ->with('place', 'media', 'client')
$replies = 0; ->withCount(['webmentions As replies' => function ($query) {
foreach ($note->webmentions as $webmention) { $query->where('type', 'in-reply-to');
if ($webmention->type == 'in-reply-to') { }])->paginate(10);
$replies++;
}
}
$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]);
$note->address = $this->reverseGeoCode((float) trim($latlng[0]), (float) trim($latlng[1]));
}
if ($note->place !== null) {
$lnglat = explode(' ', $note->place->location);
$note->latitude = $lnglat[1];
$note->longitude = $lnglat[0];
$note->address = $note->place->name;
$note->placeLink = '/places/' . $note->place->slug;
}
/*$mediaLinks = [];
foreach ($note->media()->get() as $media) {
$mediaLinks[] = $media->url;
}*/
}
$homepage = ($request->path() == '/'); return view('notes.index', compact('notes'));
return view('notes.index', compact('notes', 'homepage'));
} }
/** /**
@ -70,90 +35,9 @@ class NotesController extends Controller
*/ */
public function show($urlId) public function show($urlId)
{ {
$numbers = new Numbers(); $note = Note::nb60($urlId)->with('webmentions')->first();
$authorship = new Authorship();
$realId = $numbers->b60tonum($urlId);
$note = Note::find($realId);
$replies = [];
$reposts = [];
$likes = [];
$carbon = new \Carbon\Carbon();
foreach ($note->webmentions as $webmention) {
/*
reply->url |
reply->photo | Author
reply->name |
reply->source
reply->date
reply->reply
repost->url | return view('notes.show', compact('note'));
repost->photo | Author
repost->name |
repost->date
repost->source
like->url |
like->photo | Author
like->name |
*/
$microformats = json_decode($webmention->mf2, true);
$authorHCard = $authorship->findAuthor($microformats);
$content['url'] = $authorHCard['properties']['url'][0];
$content['photo'] = $this->createPhotoLink($authorHCard['properties']['photo'][0]);
$content['name'] = $authorHCard['properties']['name'][0];
switch ($webmention->type) {
case 'in-reply-to':
$content['source'] = $webmention->source;
if (isset($microformats['items'][0]['properties']['published'][0])) {
try {
$content['date'] = $carbon->parse(
$microformats['items'][0]['properties']['published'][0]
)->toDayDateTimeString();
} catch (\Exception $exception) {
$content['date'] = $webmention->updated_at->toDayDateTimeString();
}
} else {
$content['date'] = $webmention->updated_at->toDayDateTimeString();
}
$content['reply'] = $this->filterHTML(
$microformats['items'][0]['properties']['content'][0]['html']
);
$replies[] = $content;
break;
case 'repost-of':
$content['date'] = $carbon->parse(
$microformats['items'][0]['properties']['published'][0]
)->toDayDateTimeString();
$content['source'] = $webmention->source;
$reposts[] = $content;
break;
case 'like-of':
$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]);
$note->address = $this->reverseGeoCode((float) trim($latlng[0]), (float) trim($latlng[1]));
}
if ($note->place !== null) {
$lnglat = explode(' ', $note->place->location);
$note->latitude = $lnglat[1];
$note->longitude = $lnglat[0];
$note->address = $note->place->name;
$note->placeLink = '/places/' . $note->place->slug;
}
return view('notes.show', compact('note', 'replies', 'reposts', 'likes'));
} }
/** /**
@ -164,12 +48,7 @@ class NotesController extends Controller
*/ */
public function redirect($decId) public function redirect($decId)
{ {
$numbers = new Numbers(); return redirect(config('app.url') . '/notes/' . (new Numbers())->numto60($decId));
$realId = $numbers->numto60($decId);
$url = config('app.url') . '/notes/' . $realId;
return redirect($url);
} }
/** /**
@ -183,180 +62,7 @@ class NotesController extends Controller
$notes = Note::whereHas('tags', function ($query) use ($tag) { $notes = Note::whereHas('tags', function ($query) use ($tag) {
$query->where('tag', $tag); $query->where('tag', $tag);
})->get(); })->get();
foreach ($notes as $note) {
$note->iso8601_time = $note->updated_at->toISO8601String();
$note->human_time = $note->updated_at->diffForHumans();
}
return view('notes.tagged', compact('notes', 'tag')); return view('notes.tagged', compact('notes', 'tag'));
} }
/**
* Create the photo link.
*
* We shall leave twitter.com and twimg.com links as they are. Then we shall
* check for local copies, if that fails leave the link as is.
*
* @param string
* @return string
*/
public function createPhotoLink($url)
{
$host = parse_url($url, PHP_URL_HOST);
if ($host == 'pbs.twimg.com') {
//make sure we use HTTPS, we know twitter supports it
return str_replace('http://', 'https://', $url);
}
if ($host == 'twitter.com') {
if (Cache::has($url)) {
return Cache::get($url);
}
$username = parse_url($url, PHP_URL_PATH);
try {
$info = Twitter::getUsers(['screen_name' => $username]);
$profile_image = $info->profile_image_url_https;
Cache::put($url, $profile_image, 10080); //1 week
} catch (Exception $e) {
return $url; //not sure here
}
return $profile_image;
}
$filesystem = new Filesystem();
if ($filesystem->exists(public_path() . '/assets/profile-images/' . $host . '/image')) {
return '/assets/profile-images/' . $host . '/image';
}
return $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;
}
/**
* Filter the HTML in a reply webmention.
*
* @param string The reply HTML
* @return string The filtered HTML
*/
private function filterHTML($html)
{
$config = HTMLPurifier_Config::createDefault();
$config->set('Cache.SerializerPath', storage_path() . '/HTMLPurifier');
$config->set('HTML.TargetBlank', true);
$purifier = new HTMLPurifier($config);
return $purifier->purify($html);
}
/**
* Do a reverse geocode lookup of a `lat,lng` value.
*
* @param float The latitude
* @param float The longitude
* @return string The location HTML
*/
public function reverseGeoCode(float $latitude, float $longitude): string
{
$latlng = $latitude . ',' . $longitude;
return Cache::get($latlng, function () use ($latlng, $latitude, $longitude) {
$guzzle = new Client();
$response = $guzzle->request('GET', 'https://nominatim.openstreetmap.org/reverse', [
'query' => [
'format' => 'json',
'lat' => $latitude,
'lon' => $longitude,
'zoom' => 18,
'addressdetails' => 1,
],
'headers' => ['User-Agent' => 'jonnybarnes.uk via Guzzle, email jonny@jonnybarnes.uk'],
]);
$json = json_decode($response->getBody());
if (isset($json->address->town)) {
$address = '<span class="p-locality">'
. $json->address->town
. '</span>, <span class="p-country-name">'
. $json->address->country
. '</span>';
Cache::forever($latlng, $address);
return $address;
}
if (isset($json->address->city)) {
$address = $json->address->city . ', ' . $json->address->country;
Cache::forever($latlng, $address);
return $address;
}
if (isset($json->address->county)) {
$address = '<span class="p-region">'
. $json->address->county
. '</span>, <span class="p-country-name">'
. $json->address->country
. '</span>';
Cache::forever($latlng, $address);
return $address;
}
$adress = '<span class="p-country-name">' . $json->address->country . '</span>';
Cache::forever($latlng, $address);
return $address;
});
}
private function getGeoJson($longitude, $latitude, $title, $icon)
{
$icon = $icon ?? 'marker';
return
"{
'type': 'FeatureCollection',
'features': [{
'type': 'Feature',
'geometry': {
'type': 'Point',
'coordinates': [$longitude, $latitude]
},
'properties': {
'title': '$title',
'icon': '$icon'
}
}]
}";
}
} }

View file

@ -0,0 +1,42 @@
<?php
namespace App\Jobs;
use App\MicropubClient;
use Illuminate\Bus\Queueable;
use Illuminate\Queue\SerializesModels;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
class AddClientToDatabase implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
protected $client_id;
/**
* Create a new job instance.
*
* @return void
*/
public function __construct(string $client_id)
{
$this->client_id = $client_id;
}
/**
* Execute the job.
*
* @return void
*/
public function handle()
{
if (MicropubClient::where('client_url', $this->client_id)->count() == 0) {
$client = MicropubClient::create([
'client_url' => $this->client_id,
'client_name' => $this->client_id, // default client name is the URL
]);
}
}
}

View file

@ -3,16 +3,14 @@
namespace App\Jobs; namespace App\Jobs;
use Mf2; use Mf2;
use App\Note;
use App\WebMention;
use GuzzleHttp\Client; use GuzzleHttp\Client;
use App\{Note, WebMention};
use Illuminate\Bus\Queueable; use Illuminate\Bus\Queueable;
use Illuminate\Queue\SerializesModels;
use Illuminate\Queue\InteractsWithQueue;
use Jonnybarnes\WebmentionsParser\Parser; use Jonnybarnes\WebmentionsParser\Parser;
use GuzzleHttp\Exception\RequestException; use GuzzleHttp\Exception\RequestException;
use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Contracts\Queue\ShouldQueue;
use App\Exceptions\RemoteContentNotFoundException; use App\Exceptions\RemoteContentNotFoundException;
use Illuminate\Queue\{InteractsWithQueue, SerializesModels};
class ProcessWebMention implements ShouldQueue class ProcessWebMention implements ShouldQueue
{ {

View file

@ -19,4 +19,14 @@ class MicropubClient extends Model
* @var array * @var array
*/ */
protected $fillable = ['client_url', 'client_name']; protected $fillable = ['client_url', 'client_name'];
/**
* Define the relationship with notes.
*
* @return void
*/
public function notes()
{
return $this->hasMany('App\Note', 'client_id', 'client_url');
}
} }

View file

@ -2,7 +2,10 @@
namespace App; namespace App;
use Cache;
use Twitter;
use Normalizer; use Normalizer;
use GuzzleHttp\Client;
use Laravel\Scout\Searchable; use Laravel\Scout\Searchable;
use Jonnybarnes\IndieWeb\Numbers; use Jonnybarnes\IndieWeb\Numbers;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
@ -33,6 +36,16 @@ class Note extends Model
return $this->belongsToMany('App\Tag'); return $this->belongsToMany('App\Tag');
} }
/**
* Define the relationship with clients.
*
* @var array?
*/
public function client()
{
return $this->belongsTo('App\MicropubClient', 'client_id', 'client_url');
}
/** /**
* Define the relationship with webmentions. * Define the relationship with webmentions.
* *
@ -96,17 +109,6 @@ class Note extends Model
]; ];
} }
/**
* 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. * Normalize the note to Unicode FORM C.
* *
@ -168,6 +170,26 @@ class Note extends Model
return config('app.shorturl') . '/notes/' . $this->nb60id; return config('app.shorturl') . '/notes/' . $this->nb60id;
} }
/**
* Get the ISO8601 value for mf2.
*
* @return string
*/
public function getIso8601Attribute()
{
return $this->updated_at->toISO8601String();
}
/**
* Get the ISO8601 value for mf2.
*
* @return string
*/
public function getHumandiffAttribute()
{
return $this->updated_at->diffForHumans();
}
/** /**
* Get the pubdate value for RSS feeds. * Get the pubdate value for RSS feeds.
* *
@ -179,26 +201,85 @@ class Note extends Model
} }
/** /**
* Get the relavent client name assocaited with the client id. * Get the latitude value.
* *
* @return string|null * @return string|null
*/ */
public function getClientNameAttribute() public function getLatitudeAttribute()
{ {
if ($this->client_id == null) { if ($this->place !== null) {
$lnglat = explode(' ', $this->place->location);
return $lnglat[1];
}
if ($this->location !== null) {
$pieces = explode(':', $this->location);
$latlng = explode(',', $pieces[0]);
return trim($latlng[0]);
}
}
/**
* Get the longitude value.
*
* @return string|null
*/
public function getLongitudeAttribute()
{
if ($this->place !== null) {
$lnglat = explode(' ', $this->place->location);
return $lnglat[1];
}
if ($this->location !== null) {
$pieces = explode(':', $this->location);
$latlng = explode(',', $pieces[0]);
return trim($latlng[1]);
}
}
/**
* Get the address for a note. This is either a reverse geo-code from the
* location, or is derived from the associated place.
*
* @return string|null
*/
public function getAddressAttribute()
{
if ($this->place !== null) {
return $this->place->name;
}
if ($this->location !== null) {
return $this->reverseGeoCode((float) $this->latitude, (float) $this->longitude);
}
}
public function getTwitterAttribute()
{
if ($this->in_reply_to == null || mb_substr($this->in_reply_to, 0, 20, 'UTF-8') !== 'https://twitter.com/') {
return; return;
} }
$name = MicropubClient::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']; $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 $name; return $oEmbed;
} }
/** /**
@ -284,4 +365,61 @@ class Note extends Model
return $text; return $text;
} }
/**
* Do a reverse geocode lookup of a `lat,lng` value.
*
* @param float The latitude
* @param float The longitude
* @return string The location HTML
*/
public function reverseGeoCode(float $latitude, float $longitude): string
{
$latlng = $latitude . ',' . $longitude;
return Cache::get($latlng, function () use ($latlng, $latitude, $longitude) {
$guzzle = new Client();
$response = $guzzle->request('GET', 'https://nominatim.openstreetmap.org/reverse', [
'query' => [
'format' => 'json',
'lat' => $latitude,
'lon' => $longitude,
'zoom' => 18,
'addressdetails' => 1,
],
'headers' => ['User-Agent' => 'jonnybarnes.uk via Guzzle, email jonny@jonnybarnes.uk'],
]);
$json = json_decode($response->getBody());
if (isset($json->address->town)) {
$address = '<span class="p-locality">'
. $json->address->town
. '</span>, <span class="p-country-name">'
. $json->address->country
. '</span>';
Cache::forever($latlng, $address);
return $address;
}
if (isset($json->address->city)) {
$address = $json->address->city . ', ' . $json->address->country;
Cache::forever($latlng, $address);
return $address;
}
if (isset($json->address->county)) {
$address = '<span class="p-region">'
. $json->address->county
. '</span>, <span class="p-country-name">'
. $json->address->country
. '</span>';
Cache::forever($latlng, $address);
return $address;
}
$adress = '<span class="p-country-name">' . $json->address->country . '</span>';
Cache::forever($latlng, $address);
return $address;
});
}
} }

View file

@ -76,19 +76,6 @@ class Place extends Model
return $query->where($field, '<=', $distance)->orderBy($field); return $query->where($field, '<=', $distance)->orderBy($field);
} }
/*
* 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. * Get the latitude from the `location` property.
* *

View file

@ -4,6 +4,7 @@ declare(strict_types=1);
namespace App\Services; namespace App\Services;
use App\Jobs\AddClientToDatabase;
use Lcobucci\JWT\Signer\Hmac\Sha256; use Lcobucci\JWT\Signer\Hmac\Sha256;
use App\Exceptions\InvalidTokenException; use App\Exceptions\InvalidTokenException;
use Lcobucci\JWT\{Builder, Parser, Token}; use Lcobucci\JWT\{Builder, Parser, Token};
@ -26,6 +27,7 @@ class TokenService
->set('nonce', bin2hex(random_bytes(8))) ->set('nonce', bin2hex(random_bytes(8)))
->sign($signer, config('app.key')) ->sign($signer, config('app.key'))
->getToken(); ->getToken();
dispatch(new AddClientToDatabase($data['client_id']));
return (string) $token; return (string) $token;
} }

View file

@ -2,7 +2,14 @@
namespace App; namespace App;
use Cache;
use Twitter;
use HTMLPurifier;
use Carbon\Carbon;
use HTMLPurifier_Config;
use Illuminate\Filesystem\Filesystem;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
use Jonnybarnes\WebmentionsParser\Authorship;
class WebMention extends Model class WebMention extends Model
{ {
@ -29,4 +36,112 @@ class WebMention extends Model
* @var array * @var array
*/ */
protected $guarded = ['id']; protected $guarded = ['id'];
/**
* Get the author of the webmention.
*
* @return array
*/
public function getAuthorAttribute()
{
$authorship = new Authorship();
$hCard = $authorship->findAuthor(json_decode($this->mf2, true));
if (array_key_exists('properties', $hCard) &&
array_key_exists('photo', $hCard['properties'])
) {
$hCard['properties']['photo'][0] = $this->createPhotoLink($hCard['properties']['photo'][0]);
}
return $hCard;
}
/**
* Get the published value for the webmention.
*
* @return string
*/
public function getPublishedAttribute()
{
$microformats = json_decode($this->mf2, true);
$carbon = new Carbon();
if (isset($microformats['items'][0]['properties']['published'][0])) {
try {
$published = $carbon->parse(
$microformats['items'][0]['properties']['published'][0]
)->toDayDateTimeString();
} catch (\Exception $exception) {
$published = $webmention->updated_at->toDayDateTimeString();
}
} else {
$published = $webmention->updated_at->toDayDateTimeString();
}
return $published;
}
/**
* Get the filteres HTML of a reply.
*
* @return strin|null
*/
public function getReplyAttribute()
{
$microformats = json_decode($this->mf2, true);
if (isset($microformats['items'][0]['properties']['content'][0]['html'])) {
return $this->filterHTML($microformats['items'][0]['properties']['content'][0]['html']);
}
}
/**
* Create the photo link.
*
* @param string
* @return string
*/
public function createPhotoLink(string $url): string
{
$url = normalize_url($url);
$host = parse_url($url, PHP_URL_HOST);
if ($host == 'pbs.twimg.com') {
//make sure we use HTTPS, we know twitter supports it
return str_replace('http://', 'https://', $url);
}
if ($host == 'twitter.com') {
if (Cache::has($url)) {
return Cache::get($url);
}
$username = parse_url($url, PHP_URL_PATH);
try {
$info = Twitter::getUsers(['screen_name' => $username]);
$profile_image = $info->profile_image_url_https;
Cache::put($url, $profile_image, 10080); //1 week
} catch (Exception $e) {
return $url; //not sure here
}
return $profile_image;
}
$filesystem = new Filesystem();
if ($filesystem->exists(public_path() . '/assets/profile-images/' . $host . '/image')) {
return '/assets/profile-images/' . $host . '/image';
}
return $url;
}
/**
* Filter the HTML in a reply webmention.
*
* @param string The reply HTML
* @return string The filtered HTML
*/
private function filterHTML($html)
{
$config = HTMLPurifier_Config::createDefault();
$config->set('Cache.SerializerPath', storage_path() . '/HTMLPurifier');
$config->set('HTML.TargetBlank', true);
$purifier = new HTMLPurifier($config);
return $purifier->purify($html);
}
} }

View file

@ -1,6 +1,10 @@
# Changelog # Changelog
## Version 0.5.16 ## Version 0.5.17 (2017-06-22)
- Lots of code tidying, especially in the notes controller
- Fix issue#53 regarding uploading photos
## Version 0.5.16 (2017-06-17)
- Allow place `slug`s to be re-generated - Allow place `slug`s to be re-generated
- Add syndication links for swarm and instagram - Add syndication links for swarm and instagram
- Move bio to its own template, next step database? - Move bio to its own template, next step database?

42
composer.lock generated
View file

@ -8,16 +8,16 @@
"packages": [ "packages": [
{ {
"name": "aws/aws-sdk-php", "name": "aws/aws-sdk-php",
"version": "3.29.2", "version": "3.29.8",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/aws/aws-sdk-php.git", "url": "https://github.com/aws/aws-sdk-php.git",
"reference": "1f19f74913a31fac8e98c24cef26040a16c88a33" "reference": "c60a477ad5ba1b120d4d80cbddf97fbe36573996"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/1f19f74913a31fac8e98c24cef26040a16c88a33", "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/c60a477ad5ba1b120d4d80cbddf97fbe36573996",
"reference": "1f19f74913a31fac8e98c24cef26040a16c88a33", "reference": "c60a477ad5ba1b120d4d80cbddf97fbe36573996",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -84,7 +84,7 @@
"s3", "s3",
"sdk" "sdk"
], ],
"time": "2017-06-09T18:57:25+00:00" "time": "2017-06-19T19:14:37+00:00"
}, },
{ {
"name": "barnabywalters/mf-cleaner", "name": "barnabywalters/mf-cleaner",
@ -1580,16 +1580,16 @@
}, },
{ {
"name": "laravel/framework", "name": "laravel/framework",
"version": "v5.4.25", "version": "v5.4.27",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/laravel/framework.git", "url": "https://github.com/laravel/framework.git",
"reference": "6bcc9b1f542b3deed16d51f6aa1fe318ab407c2a" "reference": "66f5e1b37cbd66e730ea18850ded6dc0ad570404"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/laravel/framework/zipball/6bcc9b1f542b3deed16d51f6aa1fe318ab407c2a", "url": "https://api.github.com/repos/laravel/framework/zipball/66f5e1b37cbd66e730ea18850ded6dc0ad570404",
"reference": "6bcc9b1f542b3deed16d51f6aa1fe318ab407c2a", "reference": "66f5e1b37cbd66e730ea18850ded6dc0ad570404",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -1705,7 +1705,7 @@
"framework", "framework",
"laravel" "laravel"
], ],
"time": "2017-06-07T13:35:12+00:00" "time": "2017-06-15T19:08:25+00:00"
}, },
{ {
"name": "laravel/scout", "name": "laravel/scout",
@ -2144,16 +2144,16 @@
}, },
{ {
"name": "monolog/monolog", "name": "monolog/monolog",
"version": "1.22.1", "version": "1.23.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/Seldaek/monolog.git", "url": "https://github.com/Seldaek/monolog.git",
"reference": "1e044bc4b34e91743943479f1be7a1d5eb93add0" "reference": "fd8c787753b3a2ad11bc60c063cff1358a32a3b4"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/Seldaek/monolog/zipball/1e044bc4b34e91743943479f1be7a1d5eb93add0", "url": "https://api.github.com/repos/Seldaek/monolog/zipball/fd8c787753b3a2ad11bc60c063cff1358a32a3b4",
"reference": "1e044bc4b34e91743943479f1be7a1d5eb93add0", "reference": "fd8c787753b3a2ad11bc60c063cff1358a32a3b4",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -2174,7 +2174,7 @@
"phpunit/phpunit-mock-objects": "2.3.0", "phpunit/phpunit-mock-objects": "2.3.0",
"ruflin/elastica": ">=0.90 <3.0", "ruflin/elastica": ">=0.90 <3.0",
"sentry/sentry": "^0.13", "sentry/sentry": "^0.13",
"swiftmailer/swiftmailer": "~5.3" "swiftmailer/swiftmailer": "^5.3|^6.0"
}, },
"suggest": { "suggest": {
"aws/aws-sdk-php": "Allow sending log messages to AWS services like DynamoDB", "aws/aws-sdk-php": "Allow sending log messages to AWS services like DynamoDB",
@ -2218,7 +2218,7 @@
"logging", "logging",
"psr-3" "psr-3"
], ],
"time": "2017-03-13T07:08:03+00:00" "time": "2017-06-19T01:22:40+00:00"
}, },
{ {
"name": "mtdowling/cron-expression", "name": "mtdowling/cron-expression",
@ -2725,16 +2725,16 @@
}, },
{ {
"name": "psy/psysh", "name": "psy/psysh",
"version": "v0.8.6", "version": "v0.8.7",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/bobthecow/psysh.git", "url": "https://github.com/bobthecow/psysh.git",
"reference": "7028d6d525fb183d50b249b7c07598e3d386b27d" "reference": "be969b9dc89dcaefdb9a3117fa91fa38bca19f50"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/bobthecow/psysh/zipball/7028d6d525fb183d50b249b7c07598e3d386b27d", "url": "https://api.github.com/repos/bobthecow/psysh/zipball/be969b9dc89dcaefdb9a3117fa91fa38bca19f50",
"reference": "7028d6d525fb183d50b249b7c07598e3d386b27d", "reference": "be969b9dc89dcaefdb9a3117fa91fa38bca19f50",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -2794,7 +2794,7 @@
"interactive", "interactive",
"shell" "shell"
], ],
"time": "2017-06-04T10:34:20+00:00" "time": "2017-06-20T12:51:31+00:00"
}, },
{ {
"name": "ramsey/uuid", "name": "ramsey/uuid",

View file

@ -12,7 +12,7 @@ class WebMentionsTableSeeder extends Seeder
public function run() public function run()
{ {
$webmention = App\WebMention::create([ $webmention = App\WebMention::create([
'source' => 'https://aaornpk.local/reply/1', 'source' => 'https://aaornpk.localhost/reply/1',
'target' => 'https://jonnybarnes.localhost/notes/D', 'target' => 'https://jonnybarnes.localhost/notes/D',
'commentable_id' => '13', 'commentable_id' => '13',
'commentable_type' => 'App\Note', 'commentable_type' => 'App\Note',

4430
package-lock.json generated Normal file

File diff suppressed because it is too large Load diff

View file

@ -6,23 +6,23 @@
"license": "CC0-1.0", "license": "CC0-1.0",
"dependencies": { "dependencies": {
"alertify.js": "^1.0.12", "alertify.js": "^1.0.12",
"mapbox-gl": "0.37.0", "mapbox-gl": "^0.38.0",
"marked": "^0.3.6", "marked": "^0.3.6",
"normalize.css": "7.0.0", "normalize.css": "^7.0.0",
"webStorage": "^1.2.2" "webStorage": "^1.2.3"
}, },
"devDependencies": { "devDependencies": {
"babel-cli": "^6.18.0", "babel-cli": "^6.18.0",
"babel-core": "^6.21.0", "babel-core": "^6.21.0",
"babel-loader": "7.0.0", "babel-loader": "^7.1.0",
"babel-preset-env": "^1.2.2", "babel-preset-env": "^1.2.2",
"babel-preset-es2015": "^6.18.0", "babel-preset-es2015": "^6.18.0",
"babel-preset-latest": "^6.16.0", "babel-preset-latest": "^6.16.0",
"babel-runtime": "^6.20.0", "babel-runtime": "^6.20.0",
"lint-staged": "^3.2.1", "lint-staged": "^4.0.0",
"pre-commit": "^1.1.3", "pre-commit": "^1.1.3",
"stylelint-config-standard": "^16.0.0", "stylelint-config-standard": "^16.0.0",
"webpack": "^2.2.0" "webpack": "^3.0.0"
}, },
"scripts": { "scripts": {
"compress": "scripts/compress", "compress": "scripts/compress",

File diff suppressed because one or more lines are too long

View file

@ -1,2 +1,2 @@
!function(modules){function __webpack_require__(moduleId){if(installedModules[moduleId])return installedModules[moduleId].exports;var module=installedModules[moduleId]={i:moduleId,l:!1,exports:{}};return modules[moduleId].call(module.exports,module,module.exports,__webpack_require__),module.l=!0,module.exports}var installedModules={};__webpack_require__.m=modules,__webpack_require__.c=installedModules,__webpack_require__.i=function(value){return value},__webpack_require__.d=function(exports,name,getter){__webpack_require__.o(exports,name)||Object.defineProperty(exports,name,{configurable:!1,enumerable:!0,get:getter})},__webpack_require__.n=function(module){var getter=module&&module.__esModule?function(){return module.default}:function(){return module};return __webpack_require__.d(getter,"a",getter),getter},__webpack_require__.o=function(object,property){return Object.prototype.hasOwnProperty.call(object,property)},__webpack_require__.p="",__webpack_require__(__webpack_require__.s=8)}({8:function(module,exports,__webpack_require__){"use strict";var youtubeRegex=/watch\?v=([A-Za-z0-9\-_]+)\b/,spotifyRegex=/https\:\/\/play\.spotify\.com\/(.*)\b/,notes=document.querySelectorAll(".e-content"),_iteratorNormalCompletion=!0,_didIteratorError=!1,_iteratorError=void 0;try{for(var _step,_iterator=notes[Symbol.iterator]();!(_iteratorNormalCompletion=(_step=_iterator.next()).done);_iteratorNormalCompletion=!0){var note=_step.value,ytid=note.textContent.match(youtubeRegex);if(ytid){var ytcontainer=document.createElement("div");ytcontainer.classList.add("container");var ytiframe=document.createElement("iframe");ytiframe.classList.add("youtube"),ytiframe.setAttribute("src","https://www.youtube.com/embed/"+ytid[1]),ytiframe.setAttribute("frameborder",0),ytiframe.setAttribute("allowfullscreen","true"),ytcontainer.appendChild(ytiframe),note.appendChild(ytcontainer)}var spotifyid=note.textContent.match(spotifyRegex);if(spotifyid){var sid=spotifyid[1].replace("/",":"),siframe=document.createElement("iframe");siframe.classList.add("spotify"),siframe.setAttribute("src","https://embed.spotify.com/?uri=spotify:"+sid),siframe.setAttribute("frameborder",0),siframe.setAttribute("allowtransparency","true"),note.appendChild(siframe)}}}catch(err){_didIteratorError=!0,_iteratorError=err}finally{try{!_iteratorNormalCompletion&&_iterator.return&&_iterator.return()}finally{if(_didIteratorError)throw _iteratorError}}}}); !function(modules){function __webpack_require__(moduleId){if(installedModules[moduleId])return installedModules[moduleId].exports;var module=installedModules[moduleId]={i:moduleId,l:!1,exports:{}};return modules[moduleId].call(module.exports,module,module.exports,__webpack_require__),module.l=!0,module.exports}var installedModules={};__webpack_require__.m=modules,__webpack_require__.c=installedModules,__webpack_require__.d=function(exports,name,getter){__webpack_require__.o(exports,name)||Object.defineProperty(exports,name,{configurable:!1,enumerable:!0,get:getter})},__webpack_require__.n=function(module){var getter=module&&module.__esModule?function(){return module.default}:function(){return module};return __webpack_require__.d(getter,"a",getter),getter},__webpack_require__.o=function(object,property){return Object.prototype.hasOwnProperty.call(object,property)},__webpack_require__.p="",__webpack_require__(__webpack_require__.s=5)}({5:function(module,exports,__webpack_require__){"use strict";var youtubeRegex=/watch\?v=([A-Za-z0-9\-_]+)\b/,spotifyRegex=/https\:\/\/play\.spotify\.com\/(.*)\b/,notes=document.querySelectorAll(".e-content"),_iteratorNormalCompletion=!0,_didIteratorError=!1,_iteratorError=void 0;try{for(var _step,_iterator=notes[Symbol.iterator]();!(_iteratorNormalCompletion=(_step=_iterator.next()).done);_iteratorNormalCompletion=!0){var note=_step.value,ytid=note.textContent.match(youtubeRegex);if(ytid){var ytcontainer=document.createElement("div");ytcontainer.classList.add("container");var ytiframe=document.createElement("iframe");ytiframe.classList.add("youtube"),ytiframe.setAttribute("src","https://www.youtube.com/embed/"+ytid[1]),ytiframe.setAttribute("frameborder",0),ytiframe.setAttribute("allowfullscreen","true"),ytcontainer.appendChild(ytiframe),note.appendChild(ytcontainer)}var spotifyid=note.textContent.match(spotifyRegex);if(spotifyid){var sid=spotifyid[1].replace("/",":"),siframe=document.createElement("iframe");siframe.classList.add("spotify"),siframe.setAttribute("src","https://embed.spotify.com/?uri=spotify:"+sid),siframe.setAttribute("frameborder",0),siframe.setAttribute("allowtransparency","true"),note.appendChild(siframe)}}}catch(err){_didIteratorError=!0,_iteratorError=err}finally{try{!_iteratorNormalCompletion&&_iterator.return&&_iterator.return()}finally{if(_didIteratorError)throw _iteratorError}}}});
//# sourceMappingURL=links.js.map //# sourceMappingURL=links.js.map

Binary file not shown.

Binary file not shown.

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

Binary file not shown.

Binary file not shown.

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

Binary file not shown.

Binary file not shown.

File diff suppressed because one or more lines are too long

View file

@ -1,2 +1,2 @@
!function(modules){function __webpack_require__(moduleId){if(installedModules[moduleId])return installedModules[moduleId].exports;var module=installedModules[moduleId]={i:moduleId,l:!1,exports:{}};return modules[moduleId].call(module.exports,module,module.exports,__webpack_require__),module.l=!0,module.exports}var installedModules={};__webpack_require__.m=modules,__webpack_require__.c=installedModules,__webpack_require__.i=function(value){return value},__webpack_require__.d=function(exports,name,getter){__webpack_require__.o(exports,name)||Object.defineProperty(exports,name,{configurable:!1,enumerable:!0,get:getter})},__webpack_require__.n=function(module){var getter=module&&module.__esModule?function(){return module.default}:function(){return module};return __webpack_require__.d(getter,"a",getter),getter},__webpack_require__.o=function(object,property){return Object.prototype.hasOwnProperty.call(object,property)},__webpack_require__.p="",__webpack_require__(__webpack_require__.s=14)}({14:function(module,exports,__webpack_require__){"use strict";var _paq=_paq||[];_paq.push(["trackPageView"]),_paq.push(["enableLinkTracking"]),_paq.push(["setTrackerUrl","https://analytics.jmb.lv/piwik.php"]),_paq.push(["setSiteId","1"])}}); !function(modules){function __webpack_require__(moduleId){if(installedModules[moduleId])return installedModules[moduleId].exports;var module=installedModules[moduleId]={i:moduleId,l:!1,exports:{}};return modules[moduleId].call(module.exports,module,module.exports,__webpack_require__),module.l=!0,module.exports}var installedModules={};__webpack_require__.m=modules,__webpack_require__.c=installedModules,__webpack_require__.d=function(exports,name,getter){__webpack_require__.o(exports,name)||Object.defineProperty(exports,name,{configurable:!1,enumerable:!0,get:getter})},__webpack_require__.n=function(module){var getter=module&&module.__esModule?function(){return module.default}:function(){return module};return __webpack_require__.d(getter,"a",getter),getter},__webpack_require__.o=function(object,property){return Object.prototype.hasOwnProperty.call(object,property)},__webpack_require__.p="",__webpack_require__(__webpack_require__.s=16)}({16:function(module,exports,__webpack_require__){"use strict";var _paq=_paq||[];_paq.push(["trackPageView"]),_paq.push(["enableLinkTracking"]),_paq.push(["setTrackerUrl","https://analytics.jmb.lv/piwik.php"]),_paq.push(["setSiteId","1"])}});
//# sourceMappingURL=piwik.js.map //# sourceMappingURL=piwik.js.map

Binary file not shown.

Binary file not shown.

View file

@ -1 +1 @@
{"version":3,"sources":["webpack:/webpack/bootstrap 6832c5e30966f4c44816?e79d**","webpack:///piwik.js"],"names":["__webpack_require__","moduleId","installedModules","exports","module","i","l","modules","call","m","c","value","d","name","getter","o","Object","defineProperty","configurable","enumerable","get","n","__esModule","object","property","prototype","hasOwnProperty","p","s","_paq","push"],"mappings":"mBAIA,SAAAA,oBAAAC,UAGA,GAAAC,iBAAAD,UACA,OAAAC,iBAAAD,UAAAE,QAGA,IAAAC,OAAAF,iBAAAD,WACAI,EAAAJ,SACAK,GAAA,EACAH,YAUA,OANAI,QAAAN,UAAAO,KAAAJ,OAAAD,QAAAC,OAAAA,OAAAD,QAAAH,qBAGAI,OAAAE,GAAA,EAGAF,OAAAD,QAvBA,IAAAD,oBA4BAF,oBAAAS,EAAAF,QAGAP,oBAAAU,EAAAR,iBAGAF,oBAAAK,EAAA,SAAAM,OAA2C,OAAAA,OAG3CX,oBAAAY,EAAA,SAAAT,QAAAU,KAAAC,QACAd,oBAAAe,EAAAZ,QAAAU,OACAG,OAAAC,eAAAd,QAAAU,MACAK,cAAA,EACAC,YAAA,EACAC,IAAAN,UAMAd,oBAAAqB,EAAA,SAAAjB,QACA,IAAAU,OAAAV,QAAAA,OAAAkB,WACA,WAA2B,OAAAlB,OAAA,SAC3B,WAAiC,OAAAA,QAEjC,OADAJ,oBAAAY,EAAAE,OAAA,IAAAA,QACAA,QAIAd,oBAAAe,EAAA,SAAAQ,OAAAC,UAAsD,OAAAR,OAAAS,UAAAC,eAAAlB,KAAAe,OAAAC,WAGtDxB,oBAAA2B,EAAA,GAGA3B,oBAAAA,oBAAA4B,EAAA,mEC9DA,IAAIC,KAAOA,SAEXA,KAAKC,MAAM,kBACXD,KAAKC,MAAM,uBACXD,KAAKC,MAAM,gBAAiB,uCAC5BD,KAAKC,MAAM,YAAa","file":"public/assets/js/piwik.js.map","sourcesContent":[" \t// The module cache\n \tvar installedModules = {};\n\n \t// The require function\n \tfunction __webpack_require__(moduleId) {\n\n \t\t// Check if module is in cache\n \t\tif(installedModules[moduleId]) {\n \t\t\treturn installedModules[moduleId].exports;\n \t\t}\n \t\t// Create a new module (and put it into the cache)\n \t\tvar module = installedModules[moduleId] = {\n \t\t\ti: moduleId,\n \t\t\tl: false,\n \t\t\texports: {}\n \t\t};\n\n \t\t// Execute the module function\n \t\tmodules[moduleId].call(module.exports, module, module.exports, __webpack_require__);\n\n \t\t// Flag the module as loaded\n \t\tmodule.l = true;\n\n \t\t// Return the exports of the module\n \t\treturn module.exports;\n \t}\n\n\n \t// expose the modules object (__webpack_modules__)\n \t__webpack_require__.m = modules;\n\n \t// expose the module cache\n \t__webpack_require__.c = installedModules;\n\n \t// identity function for calling harmony imports with the correct context\n \t__webpack_require__.i = function(value) { return value; };\n\n \t// define getter function for harmony exports\n \t__webpack_require__.d = function(exports, name, getter) {\n \t\tif(!__webpack_require__.o(exports, name)) {\n \t\t\tObject.defineProperty(exports, name, {\n \t\t\t\tconfigurable: false,\n \t\t\t\tenumerable: true,\n \t\t\t\tget: getter\n \t\t\t});\n \t\t}\n \t};\n\n \t// getDefaultExport function for compatibility with non-harmony modules\n \t__webpack_require__.n = function(module) {\n \t\tvar getter = module && module.__esModule ?\n \t\t\tfunction getDefault() { return module['default']; } :\n \t\t\tfunction getModuleExports() { return module; };\n \t\t__webpack_require__.d(getter, 'a', getter);\n \t\treturn getter;\n \t};\n\n \t// Object.prototype.hasOwnProperty.call\n \t__webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };\n\n \t// __webpack_public_path__\n \t__webpack_require__.p = \"\";\n\n \t// Load entry module and return exports\n \treturn __webpack_require__(__webpack_require__.s = 14);\n\n\n\n// WEBPACK FOOTER //\n// webpack/bootstrap 6832c5e30966f4c44816","// Piwik in its own js file to allow usage with a CSP policy\n\nvar _paq = _paq || [];\n// tracker methods like \"setCustomDimension\" should be called before \"trackPageView\"\n_paq.push(['trackPageView']);\n_paq.push(['enableLinkTracking']);\n_paq.push(['setTrackerUrl', 'https://analytics.jmb.lv/piwik.php']);\n_paq.push(['setSiteId', '1']);\n\n\n\n// WEBPACK FOOTER //\n// ./piwik.js"]} {"version":3,"sources":["webpack:/webpack/bootstrap 9d7c9d0c3e1e7b963a9f?3a34**","webpack:///piwik.js"],"names":["__webpack_require__","moduleId","installedModules","exports","module","i","l","modules","call","m","c","d","name","getter","o","Object","defineProperty","configurable","enumerable","get","n","__esModule","default","object","property","prototype","hasOwnProperty","p","s","16","_paq","push"],"mappings":"mBAIA,SAAAA,oBAAAC,UAGA,GAAAC,iBAAAD,UACA,OAAAC,iBAAAD,UAAAE,QAGA,IAAAC,OAAAF,iBAAAD,WACAI,EAAAJ,SACAK,GAAAA,EACAH,YAUA,OANAI,QAAAN,UAAAO,KAAAJ,OAAAD,QAAAC,OAAAA,OAAAD,QAAAH,qBAGAI,OAAAE,GAAAA,EAGAF,OAAAD,QAvBA,IAAAD,oBA4BAF,oBAAAS,EAAAF,QAGAP,oBAAAU,EAAAR,iBAGAF,oBAAAW,EAAA,SAAAR,QAAAS,KAAAC,QACAb,oBAAAc,EAAAX,QAAAS,OACAG,OAAAC,eAAAb,QAAAS,MACAK,cAAAA,EACAC,YAAAA,EACAC,IAAAN,UAMAb,oBAAAoB,EAAA,SAAAhB,QACA,IAAAS,OAAAT,QAAAA,OAAAiB,WACA,WAA2B,OAAAjB,OAAAkB,SAC3B,WAAiC,OAAAlB,QAEjC,OADAJ,oBAAAW,EAAAE,OAAA,IAAAA,QACAA,QAIAb,oBAAAc,EAAA,SAAAS,OAAAC,UAAsD,OAAAT,OAAAU,UAAAC,eAAAlB,KAAAe,OAAAC,WAGtDxB,oBAAA2B,EAAA,GAGA3B,oBAAAA,oBAAA4B,EAAA,MAAAC,GAAA,SAAAzB,OAAAD,QAAAH,qBAAA,aC3DA,IAAI8B,KAAOA,SAEXA,KAAKC,MAAM,kBACXD,KAAKC,MAAM,uBACXD,KAAKC,MAAM,gBAAiB,uCAC5BD,KAAKC,MAAM,YAAa","file":"public/assets/js/piwik.js.map","sourcesContent":[" \t// The module cache\n \tvar installedModules = {};\n\n \t// The require function\n \tfunction __webpack_require__(moduleId) {\n\n \t\t// Check if module is in cache\n \t\tif(installedModules[moduleId]) {\n \t\t\treturn installedModules[moduleId].exports;\n \t\t}\n \t\t// Create a new module (and put it into the cache)\n \t\tvar module = installedModules[moduleId] = {\n \t\t\ti: moduleId,\n \t\t\tl: false,\n \t\t\texports: {}\n \t\t};\n\n \t\t// Execute the module function\n \t\tmodules[moduleId].call(module.exports, module, module.exports, __webpack_require__);\n\n \t\t// Flag the module as loaded\n \t\tmodule.l = true;\n\n \t\t// Return the exports of the module\n \t\treturn module.exports;\n \t}\n\n\n \t// expose the modules object (__webpack_modules__)\n \t__webpack_require__.m = modules;\n\n \t// expose the module cache\n \t__webpack_require__.c = installedModules;\n\n \t// define getter function for harmony exports\n \t__webpack_require__.d = function(exports, name, getter) {\n \t\tif(!__webpack_require__.o(exports, name)) {\n \t\t\tObject.defineProperty(exports, name, {\n \t\t\t\tconfigurable: false,\n \t\t\t\tenumerable: true,\n \t\t\t\tget: getter\n \t\t\t});\n \t\t}\n \t};\n\n \t// getDefaultExport function for compatibility with non-harmony modules\n \t__webpack_require__.n = function(module) {\n \t\tvar getter = module && module.__esModule ?\n \t\t\tfunction getDefault() { return module['default']; } :\n \t\t\tfunction getModuleExports() { return module; };\n \t\t__webpack_require__.d(getter, 'a', getter);\n \t\treturn getter;\n \t};\n\n \t// Object.prototype.hasOwnProperty.call\n \t__webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };\n\n \t// __webpack_public_path__\n \t__webpack_require__.p = \"\";\n\n \t// Load entry module and return exports\n \treturn __webpack_require__(__webpack_require__.s = 16);\n\n\n\n// WEBPACK FOOTER //\n// webpack/bootstrap 9d7c9d0c3e1e7b963a9f","// Piwik in its own js file to allow usage with a CSP policy\n\nvar _paq = _paq || [];\n// tracker methods like \"setCustomDimension\" should be called before \"trackPageView\"\n_paq.push(['trackPageView']);\n_paq.push(['enableLinkTracking']);\n_paq.push(['setTrackerUrl', 'https://analytics.jmb.lv/piwik.php']);\n_paq.push(['setSiteId', '1']);\n\n\n\n// WEBPACK FOOTER //\n// ./piwik.js"]}

File diff suppressed because one or more lines are too long

Binary file not shown.

Binary file not shown.

File diff suppressed because one or more lines are too long

View file

@ -18,6 +18,8 @@ Notes «
{!! $notes->render() !!} {!! $notes->render() !!}
@stop @stop
@if (Request::path() == '/')@include('templates.bio')@endif
@section('scripts') @section('scripts')
<script defer src="/assets/js/links.js"></script> <script defer src="/assets/js/links.js"></script>
@ -27,5 +29,3 @@ Notes «
<script defer src="/assets/prism/prism.js"></script> <script defer src="/assets/prism/prism.js"></script>
<link rel="stylesheet" href="/assets/prism/prism.css"> <link rel="stylesheet" href="/assets/prism/prism.css">
@stop @stop
@if ($homepage === true)@include('templates.bio')@endif

View file

@ -7,26 +7,38 @@
@section('content') @section('content')
<div class="h-entry"> <div class="h-entry">
@include('templates.note', ['note' => $note]) @include('templates.note', ['note' => $note])
@foreach($replies as $reply) @foreach($note->webmentions->filter(function ($webmention) {
return ($webmention->type == 'in-reply-to');
}) as $reply)
<div class="u-comment h-cite"> <div class="u-comment h-cite">
<a class="u-author h-card mini-h-card" href="{{ $reply['url'] }}"> <a class="u-author h-card mini-h-card" href="{{ $reply['author']['properties']['url'][0] }}">
<img src="{{ $reply['photo'] }}" alt="" class="photo u-photo logo"> <span class="fn">{{ $reply['name'] }}</span> <img src="{{ $reply['author']['properties']['photo'][0] }}" alt="" class="photo u-photo logo"> <span class="fn">{{ $reply['author']['properties']['name'][0] }}</span>
</a> said at <a class="dt-published u-url" href="{{ $reply['source'] }}">{{ $reply['date'] }}</a> </a> said at <a class="dt-published u-url" href="{{ $reply['source'] }}">{{ $reply['published'] }}</a>
<div class="e-content p-name"> <div class="e-content p-name">
{!! $reply['reply'] !!} {!! $reply['reply'] !!}
</div> </div>
</div> </div>
@endforeach @endforeach
@if(count($likes) > 0)<h1 class="notes-subtitle">Likes</h1>@endif @if($note->webmentions->filter(function ($webmention) {
@foreach($likes as $like) return ($webmention->type == 'like-of');
<a href="{{ $like['url'] }}"><img src="{{ $like['photo'] }}" alt="profile picture of {{ $like['name'] }}" class="like-photo"></a> })->count() > 0)<h1 class="notes-subtitle">Likes</h1>
@foreach($note->webmentions->filter(function ($webmention) {
return ($webmention->type == 'like-of');
}) as $like)
<a href="{{ $like['author']['properties']['url'][0] }}"><img src="{{ $like['author']['properties']['photo'][0] }}" alt="profile picture of {{ $like['author']['properties']['name'][0] }}" class="like-photo"></a>
@endforeach @endforeach
@if(count($reposts) > 0)<h1 class="notes-subtitle">Reposts</h1>@endif @endif
@foreach($reposts as $repost) @if($note->webmentions->filter(function ($webmention) {
<p><a class="h-card vcard mini-h-card p-author" href="{{ $repost['url'] }}"> return ($webmention->type == 'repost-of');
<img src="{{ $repost['photo'] }}" alt="profile picture of {{ $repost['name'] }}" class="photo u-photo logo"> <span class="fn">{{ $repost['name'] }}</span> })->count() > 0)<h1 class="notes-subtitle">Reposts</h1>
</a> reposted this at <a href="{{ $repost['source'] }}">{{ $repost['date'] }}</a>.</p> @foreach($note->webmentions->filter(function ($webmention) {
return ($webmention->type == 'repost-of');
}) as $repost)
<p><a class="h-card vcard mini-h-card p-author" href="{{ $repost['author']['properties']['url'][0] }}">
<img src="{{ $repost['author']['properties']['photo'][0] }}" alt="profile picture of {{ $repost['author']['properties']['name'][0] }}" class="photo u-photo logo"> <span class="fn">{{ $repost['author']['properties']['name'][0] }}</span>
</a> reposted this at <a href="{{ $repost['source'] }}">{{ $repost['published'] }}</a>.</p>
@endforeach @endforeach
@endif
<!-- these empty tags are for https://brid.gys publishing service --> <!-- these empty tags are for https://brid.gys publishing service -->
<a href="https://brid.gy/publish/twitter"></a> <a href="https://brid.gy/publish/twitter"></a>
<a href="https://brid.gy/publish/facebook"></a> <a href="https://brid.gy/publish/facebook"></a>

View file

@ -1,13 +1,13 @@
@extends('master') @extends('master')
@section('title') @section('title')
Tagged Notes « Tagged Notes «
@stop @stop
@section('content') @section('content')
<h2>Notes tagged with <em>{{ $tag }}</em></h2> <h2>Notes tagged with <em>{{ $tag }}</em></h2>
@foreach ($notes as $note) @foreach ($notes as $note)
<div>{!! $note->note !!} <div>{!! $note->note !!}
<a href="/note/{{ $note->id }}">{{ $note->human_time }}</a></div> <a href="/note/{{ $note->id }}">{{ $note->humandiff }}</a></div>
@endforeach @endforeach
@stop @stop

View file

@ -8,7 +8,7 @@
<div class="note"> <div class="note">
<div class="e-content p-name"> <div class="e-content p-name">
{!! $note->note !!} {!! $note->note !!}
@foreach($note->media()->get() as $media) @foreach($note->media as $media)
@if($media->type == 'image')<img class="u-photo" src="{{ $media->url }}" alt="">@endif @if($media->type == 'image')<img class="u-photo" src="{{ $media->url }}" alt="">@endif
@if($media->type == 'audio')<audio class="u-audio" src="{{ $media->url }}" controls>@endif @if($media->type == 'audio')<audio class="u-audio" src="{{ $media->url }}" controls>@endif
@if($media->type == 'video')<video class="u-video" src="{{ $media->url }}" controls>@endif @if($media->type == 'video')<video class="u-video" src="{{ $media->url }}" controls>@endif
@ -17,10 +17,10 @@
</div> </div>
<div class="note-metadata"> <div class="note-metadata">
<div> <div>
<a class="u-url" href="/notes/{{ $note->nb60id }}"><time class="dt-published" datetime="{{ $note->iso8601_time }}">{{ $note->human_time }}</time></a>@if($note->client_name) via <a class="client" href="{{ $note->client_id }}">{{ $note->client_name }}</a>@endif <a class="u-url" href="/notes/{{ $note->nb60id }}"><time class="dt-published" datetime="{{ $note->iso8601 }}" title="{{ $note->iso8601 }}">{{ $note->humandiff }}</time></a>@if($note->client) via <a class="client" href="{{ $note->client->client_url }}">{{ $note->client->client_name }}</a>@endif
@if($note->placeLink)in <span class="p-location h-card"><a class="p-name u-url" href="{{ $note->placeLink }}">{{ $note->address }}</a><data class="p-latitude" value="{{ $note->latitude }}"></data><data class="p-longitude" value="{{ $note->longitude }}"></data></span> @if($note->place)in <span class="p-location h-card"><a class="p-name u-url" href="{{ $note->place->longurl }}">{{ $note->address }}</a><data class="p-latitude" value="{{ $note->place->latitude }}"></data><data class="p-longitude" value="{{ $note->place->longitude }}"></data></span>
@elseif($note->address)in <span class="p-location h-adr">{!! $note->address !!}<data class="p-latitude" value="{{ $note->latitude }}"></data><data class="p-longitude" value="{{ $note->longitude }}"></data></span>@endif @elseif($note->address)in <span class="p-location h-adr">{!! $note->address !!}<data class="p-latitude" value="{{ $note->latitude }}"></data><data class="p-longitude" value="{{ $note->longitude }}"></data></span>@endif
@if($note->replies > 0) @include('templates.replies-icon'): {{ $note->replies }}@endif @if($note->replies_count > 0) @include('templates.replies-icon'): {{ $note->replies_count }}@endif
</div> </div>
<div class="social-links"> <div class="social-links">
@if( @if(
@ -37,10 +37,10 @@
@endif @endif
</div> </div>
</div> </div>
@if ($note->placeLink) @if ($note->place)
<div class="map" <div class="map"
data-latitude="{{ $note->latitude }}" data-latitude="{{ $note->place->latitude }}"
data-longitude="{{ $note->longitude }}" data-longitude="{{ $note->place->longitude }}"
data-name="{{ $note->place->name }}" data-name="{{ $note->place->name }}"
data-marker="{{ $note->place->icon }}"></div> data-marker="{{ $note->place->icon }}"></div>
@endif @endif

View file

@ -2,21 +2,14 @@
namespace Tests\Unit; namespace Tests\Unit;
use Cache;
use App\WebMention;
use Tests\TestCase; use Tests\TestCase;
use Illuminate\Support\Facades\Cache;
use App\Http\Controllers\NotesController;
use Illuminate\Foundation\Testing\DatabaseMigrations; use Illuminate\Foundation\Testing\DatabaseMigrations;
use Illuminate\Foundation\Testing\DatabaseTransactions; use Illuminate\Foundation\Testing\DatabaseTransactions;
class NotesControllerTest extends TestCase class WebMentionTest extends TestCase
{ {
protected $notesController;
public function __construct()
{
$this->notesController = new NotesController();
}
/** /**
* Test a correct profile link is formed from a generic URL. * Test a correct profile link is formed from a generic URL.
* *
@ -24,10 +17,10 @@ class NotesControllerTest extends TestCase
*/ */
public function test_create_photo_link_with_non_cached_image() public function test_create_photo_link_with_non_cached_image()
{ {
$notesController = new \App\Http\Controllers\NotesController(); $webmention = new WebMention();
$homepage = 'https://example.org/profile.png'; $homepage = 'https://example.org/profile.png';
$expected = 'https://example.org/profile.png'; $expected = 'https://example.org/profile.png';
$this->assertEquals($expected, $this->notesController->createPhotoLink($homepage)); $this->assertEquals($expected, $webmention->createPhotoLink($homepage));
} }
/** /**
@ -37,10 +30,10 @@ class NotesControllerTest extends TestCase
*/ */
public function test_create_photo_link_with_cached_image() public function test_create_photo_link_with_cached_image()
{ {
$notesController = new \App\Http\Controllers\NotesController(); $webmention = new WebMention();
$homepage = 'https://aaronparecki.com/profile.png'; $homepage = 'https://aaronparecki.com/profile.png';
$expected = '/assets/profile-images/aaronparecki.com/image'; $expected = '/assets/profile-images/aaronparecki.com/image';
$this->assertEquals($expected, $this->notesController->createPhotoLink($homepage)); $this->assertEquals($expected, $webmention->createPhotoLink($homepage));
} }
/** /**
@ -50,10 +43,10 @@ class NotesControllerTest extends TestCase
*/ */
public function test_create_photo_link_with_twimg_profile_image_url() public function test_create_photo_link_with_twimg_profile_image_url()
{ {
$notesController = new \App\Http\Controllers\NotesController(); $webmention = new WebMention();
$twitterProfileImage = 'http://pbs.twimg.com/1234'; $twitterProfileImage = 'http://pbs.twimg.com/1234';
$expected = 'https://pbs.twimg.com/1234'; $expected = 'https://pbs.twimg.com/1234';
$this->assertEquals($expected, $this->notesController->createPhotoLink($twitterProfileImage)); $this->assertEquals($expected, $webmention->createPhotoLink($twitterProfileImage));
} }
/** /**
@ -63,9 +56,10 @@ class NotesControllerTest extends TestCase
*/ */
public function test_create_photo_link_with_cached_twitter_url() public function test_create_photo_link_with_cached_twitter_url()
{ {
$webmention = new WebMention();
$twitterURL = 'https://twitter.com/example'; $twitterURL = 'https://twitter.com/example';
$expected = 'https://pbs.twimg.com/static_profile_link.jpg'; $expected = 'https://pbs.twimg.com/static_profile_link.jpg';
Cache::put($twitterURL, $expected, 1); Cache::put($twitterURL, $expected, 1);
$this->assertEquals($expected, $this->notesController->createPhotoLink($twitterURL)); $this->assertEquals($expected, $webmention->createPhotoLink($twitterURL));
} }
} }

4064
yarn.lock

File diff suppressed because it is too large Load diff