diff options
| -rw-r--r-- | controllers/auth.php | 161 | ||||
| -rw-r--r-- | controllers/controllers.php | 159 | ||||
| -rw-r--r-- | lib/helpers.php | 33 | ||||
| -rw-r--r-- | public/js/script.js | 8 | ||||
| -rw-r--r-- | views/dashboard.php | 20 | ||||
| -rw-r--r-- | views/index.php | 19 | ||||
| -rw-r--r-- | views/new-bookmark.php | 10 | ||||
| -rw-r--r-- | views/new-post.php | 40 | ||||
| -rw-r--r-- | views/review.php | 257 | 
9 files changed, 517 insertions, 190 deletions
| diff --git a/controllers/auth.php b/controllers/auth.php index a02e047..6af9ac4 100644 --- a/controllers/auth.php +++ b/controllers/auth.php @@ -4,19 +4,6 @@ function buildRedirectURI($params = array()) {    return Config::$base_url . 'auth/callback?' . http_build_query($params);  } -function build_url($parsed_url) { -  $scheme   = isset($parsed_url['scheme']) ? $parsed_url['scheme'] . '://' : ''; -  $host     = isset($parsed_url['host']) ? $parsed_url['host'] : ''; -  $port     = isset($parsed_url['port']) ? ':' . $parsed_url['port'] : ''; -  $user     = isset($parsed_url['user']) ? $parsed_url['user'] : ''; -  $pass     = isset($parsed_url['pass']) ? ':' . $parsed_url['pass']  : ''; -  $pass     = ($user || $pass) ? "$pass@" : ''; -  $path     = isset($parsed_url['path']) ? $parsed_url['path'] : ''; -  $query    = isset($parsed_url['query']) ? '?' . $parsed_url['query'] : ''; -  $fragment = isset($parsed_url['fragment']) ? '#' . $parsed_url['fragment'] : ''; -  return "$scheme$user$pass$host$port$path$query$fragment"; -} -  $app->get('/', function($format='html') use($app) {    $res = $app->response();    $params = $app->request()->params(); @@ -264,3 +251,151 @@ $app->get('/signout', function() use($app) {    unset($_SESSION['user_id']);    $app->redirect('/', 301);  }); + + +/* +$app->post('/auth/facebook', function() use($app) { +  if($user=require_login($app, false)) { +    $params = $app->request()->params(); +    // User just auth'd with facebook, store the access token +    $user->facebook_access_token = $params['fb_token']; +    $user->save(); + +    $app->response()->body(json_encode(array( +      'result' => 'ok' +    ))); +  } else { +    $app->response()->body(json_encode(array( +      'result' => 'error' +    ))); +  } +}); +*/ + +$app->post('/auth/twitter', function() use($app) { +  if($user=require_login($app, false)) { +    $params = $app->request()->params(); +    // User just auth'd with twitter, store the access token +    $user->twitter_access_token = $params['twitter_token']; +    $user->twitter_token_secret = $params['twitter_secret']; +    $user->save(); + +    $app->response()['Content-type'] = 'application/json'; +    $app->response()->body(json_encode(array( +      'result' => 'ok' +    ))); +  } else { +    $app->response()['Content-type'] = 'application/json'; +    $app->response()->body(json_encode(array( +      'result' => 'error' +    ))); +  } +}); + +function getTwitterLoginURL(&$twitter) { +  $request_token = $twitter->getRequestToken(Config::$base_url . 'auth/twitter/callback'); +  $_SESSION['twitter_auth'] = $request_token; +  return $twitter->getAuthorizeURL($request_token['oauth_token']); +} + +$app->get('/auth/twitter', function() use($app) { +  $params = $app->request()->params(); +  if($user=require_login($app, false)) { + +    // If there is an existing Twitter token, check if it is valid +    // Otherwise, generate a Twitter login link +    $twitter_login_url = false; +    $twitter = new \TwitterOAuth\Api(Config::$twitterClientID, Config::$twitterClientSecret, +      $user->twitter_access_token, $user->twitter_token_secret); + +    if(array_key_exists('login', $params)) { +      $twitter = new \TwitterOAuth\Api(Config::$twitterClientID, Config::$twitterClientSecret); +      $twitter_login_url = getTwitterLoginURL($twitter); +    } else { +      if($user->twitter_access_token) { +        if ($twitter->get('account/verify_credentials')) { +          $app->response()['Content-type'] = 'application/json'; +          $app->response()->body(json_encode(array( +            'result' => 'ok' +          ))); +          return; +        } else { +          // If the existing twitter token is not valid, generate a login link +          $twitter_login_url = getTwitterLoginURL($twitter); +        } +      } else { +        $twitter_login_url = getTwitterLoginURL($twitter); +      } +    } + +    $app->response()['Content-type'] = 'application/json'; +    $app->response()->body(json_encode(array( +      'url' => $twitter_login_url +    ))); + +  } else { +    $app->response()['Content-type'] = 'application/json'; +    $app->response()->body(json_encode(array( +      'result' => 'error' +    ))); +  } +}); + +$app->get('/auth/twitter/callback', function() use($app) { +  if($user=require_login($app)) { +    $params = $app->request()->params(); + +    $twitter = new \TwitterOAuth\Api(Config::$twitterClientID, Config::$twitterClientSecret, +      $_SESSION['twitter_auth']['oauth_token'], $_SESSION['twitter_auth']['oauth_token_secret']); +    $credentials = $twitter->getAccessToken($params['oauth_verifier']); + +    $user->twitter_access_token = $credentials['oauth_token']; +    $user->twitter_token_secret = $credentials['oauth_token_secret']; +    $user->twitter_username = $credentials['screen_name']; +    $user->save(); + +    $app->redirect('/settings'); +  } +}); + +$app->get('/auth/instagram', function() use($app) { +  if($user=require_login($app, false)) { + +    $instagram = instagram_client(); + +    // If there is an existing Instagram auth token, check if it's valid +    if($user->instagram_access_token) { +      $instagram->setAccessToken($user->instagram_access_token); +      $igUser = $instagram->getUser(); + +      if($igUser && $igUser->meta->code == 200) { +        $app->response()['Content-type'] = 'application/json'; +        $app->response()->body(json_encode(array( +          'result' => 'ok', +          'username' => $igUser->data->username, +          'url' => $instagram->getLoginUrl(array('basic','likes')) +        ))); +        return; +      } +    } + +    $app->response()['Content-type'] = 'application/json'; +    $app->response()->body(json_encode(array( +      'result' => 'error', +      'url' => $instagram->getLoginUrl(array('basic','likes')) +    ))); +  } +}); + +$app->get('/auth/instagram/callback', function() use($app) { +  if($user=require_login($app)) { +    $params = $app->request()->params(); + +    $instagram = instagram_client(); +    $data = $instagram->getOAuthToken($params['code']); +    $user->instagram_access_token = $data->access_token; +    $user->save(); + +    $app->redirect('/settings'); +  } +}); diff --git a/controllers/controllers.php b/controllers/controllers.php index 7565b4d..d6cad4f 100644 --- a/controllers/controllers.php +++ b/controllers/controllers.php @@ -178,6 +178,18 @@ $app->get('/photo', function() use($app) {    }  }); +$app->get('/review', function() use($app) { +  if($user=require_login($app)) { +    $params = $app->request()->params(); + +    $html = render('review', array( +      'title' => 'Review', +      'authorizing' => false +    )); +    $app->response()->body($html); +  } +}); +  $app->get('/repost', function() use($app) {    if($user=require_login($app)) {      $params = $app->request()->params(); @@ -571,150 +583,3 @@ $app->post('/micropub/postjson', function() use($app) {      )));    }  }); - -/* -$app->post('/auth/facebook', function() use($app) { -  if($user=require_login($app, false)) { -    $params = $app->request()->params(); -    // User just auth'd with facebook, store the access token -    $user->facebook_access_token = $params['fb_token']; -    $user->save(); - -    $app->response()->body(json_encode(array( -      'result' => 'ok' -    ))); -  } else { -    $app->response()->body(json_encode(array( -      'result' => 'error' -    ))); -  } -}); -*/ - -$app->post('/auth/twitter', function() use($app) { -  if($user=require_login($app, false)) { -    $params = $app->request()->params(); -    // User just auth'd with twitter, store the access token -    $user->twitter_access_token = $params['twitter_token']; -    $user->twitter_token_secret = $params['twitter_secret']; -    $user->save(); - -    $app->response()['Content-type'] = 'application/json'; -    $app->response()->body(json_encode(array( -      'result' => 'ok' -    ))); -  } else { -    $app->response()['Content-type'] = 'application/json'; -    $app->response()->body(json_encode(array( -      'result' => 'error' -    ))); -  } -}); - -function getTwitterLoginURL(&$twitter) { -  $request_token = $twitter->getRequestToken(Config::$base_url . 'auth/twitter/callback'); -  $_SESSION['twitter_auth'] = $request_token; -  return $twitter->getAuthorizeURL($request_token['oauth_token']); -} - -$app->get('/auth/twitter', function() use($app) { -  $params = $app->request()->params(); -  if($user=require_login($app, false)) { - -    // If there is an existing Twitter token, check if it is valid -    // Otherwise, generate a Twitter login link -    $twitter_login_url = false; -    $twitter = new \TwitterOAuth\Api(Config::$twitterClientID, Config::$twitterClientSecret, -      $user->twitter_access_token, $user->twitter_token_secret); - -    if(array_key_exists('login', $params)) { -      $twitter = new \TwitterOAuth\Api(Config::$twitterClientID, Config::$twitterClientSecret); -      $twitter_login_url = getTwitterLoginURL($twitter); -    } else { -      if($user->twitter_access_token) { -        if ($twitter->get('account/verify_credentials')) { -          $app->response()['Content-type'] = 'application/json'; -          $app->response()->body(json_encode(array( -            'result' => 'ok' -          ))); -          return; -        } else { -          // If the existing twitter token is not valid, generate a login link -          $twitter_login_url = getTwitterLoginURL($twitter); -        } -      } else { -        $twitter_login_url = getTwitterLoginURL($twitter); -      } -    } - -    $app->response()['Content-type'] = 'application/json'; -    $app->response()->body(json_encode(array( -      'url' => $twitter_login_url -    ))); - -  } else { -    $app->response()['Content-type'] = 'application/json'; -    $app->response()->body(json_encode(array( -      'result' => 'error' -    ))); -  } -}); - -$app->get('/auth/twitter/callback', function() use($app) { -  if($user=require_login($app)) { -    $params = $app->request()->params(); - -    $twitter = new \TwitterOAuth\Api(Config::$twitterClientID, Config::$twitterClientSecret, -      $_SESSION['twitter_auth']['oauth_token'], $_SESSION['twitter_auth']['oauth_token_secret']); -    $credentials = $twitter->getAccessToken($params['oauth_verifier']); - -    $user->twitter_access_token = $credentials['oauth_token']; -    $user->twitter_token_secret = $credentials['oauth_token_secret']; -    $user->twitter_username = $credentials['screen_name']; -    $user->save(); - -    $app->redirect('/settings'); -  } -}); - -$app->get('/auth/instagram', function() use($app) { -  if($user=require_login($app, false)) { - -    $instagram = instagram_client(); - -    // If there is an existing Instagram auth token, check if it's valid -    if($user->instagram_access_token) { -      $instagram->setAccessToken($user->instagram_access_token); -      $igUser = $instagram->getUser(); - -      if($igUser && $igUser->meta->code == 200) { -        $app->response()['Content-type'] = 'application/json'; -        $app->response()->body(json_encode(array( -          'result' => 'ok', -          'username' => $igUser->data->username, -          'url' => $instagram->getLoginUrl(array('basic','likes')) -        ))); -        return; -      } -    } - -    $app->response()['Content-type'] = 'application/json'; -    $app->response()->body(json_encode(array( -      'result' => 'error', -      'url' => $instagram->getLoginUrl(array('basic','likes')) -    ))); -  } -}); - -$app->get('/auth/instagram/callback', function() use($app) { -  if($user=require_login($app)) { -    $params = $app->request()->params(); - -    $instagram = instagram_client(); -    $data = $instagram->getOAuthToken($params['code']); -    $user->instagram_access_token = $data->access_token; -    $user->save(); - -    $app->redirect('/settings'); -  } -}); diff --git a/lib/helpers.php b/lib/helpers.php index cb9edd0..81ad38b 100644 --- a/lib/helpers.php +++ b/lib/helpers.php @@ -97,8 +97,9 @@ function micropub_post_for_user(&$user, $params, $file_path = NULL, $json = fals    $user->last_micropub_response_date = date('Y-m-d H:i:s');    // Check the response and look for a "Location" header containing the URL -  if($r['response'] && preg_match('/Location: (.+)/', $r['response'], $match)) { -    $r['location'] = trim($match[1]); +  if($r['response'] && ($r['code'] == 201 || $r['code'] == 202)  +    && isset($r['headers']['Location'])) { +    $r['location'] = $r['headers']['Location'][0];      $user->micropub_success = 1;    } else {      $r['location'] = false; @@ -168,10 +169,16 @@ function micropub_post($endpoint, $params, $access_token, $file_path = NULL, $js    $response = curl_exec($ch);    $error = curl_error($ch);    $sent_headers = curl_getinfo($ch, CURLINFO_HEADER_OUT); + +  $header_size = curl_getinfo($ch, CURLINFO_HEADER_SIZE); +  $header_str = trim(substr($response, 0, $header_size)); +    $request = $sent_headers . (is_string($post) ? $post : http_build_query($post));    return array(      'request' => $request,      'response' => $response, +    'code' => curl_getinfo($ch, CURLINFO_HTTP_CODE), +    'headers' => parse_headers($header_str),      'error' => $error,      'curlinfo' => curl_getinfo($ch)    ); @@ -207,6 +214,28 @@ function micropub_get($endpoint, $params, $access_token) {    );  } +function parse_headers($headers) { +  $retVal = array(); +  $fields = explode("\r\n", preg_replace('/\x0D\x0A[\x09\x20]+/', ' ', $headers)); +  foreach($fields as $field) { +    if(preg_match('/([^:]+): (.+)/m', $field, $match)) { +      $match[1] = preg_replace_callback('/(?<=^|[\x09\x20\x2D])./', function($m) { +        return strtoupper($m[0]); +      }, strtolower(trim($match[1]))); +      // If there's already a value set for the header name being returned, turn it into an array and add the new value +      $match[1] = preg_replace_callback('/(?<=^|[\x09\x20\x2D])./', function($m) { +        return strtoupper($m[0]); +      }, strtolower(trim($match[1]))); +      if(isset($retVal[$match[1]])) { +        $retVal[$match[1]][] = trim($match[2]); +      } else { +        $retVal[$match[1]] = [trim($match[2])]; +      } +    } +  } +  return $retVal; +} +  function get_micropub_config(&$user, $query=[]) {    $targets = []; diff --git a/public/js/script.js b/public/js/script.js index 50496d3..f6f3a48 100644 --- a/public/js/script.js +++ b/public/js/script.js @@ -39,3 +39,11 @@ $(function(){    });  }) + +function auto_prefix_url_field(field) { +  var str = field.value; +  if(!/^https?:\/\//.test(str)) { +    str = "http://" + str; +  } +  field.value = str; +} diff --git a/views/dashboard.php b/views/dashboard.php index a20a44b..59cd41e 100644 --- a/views/dashboard.php +++ b/views/dashboard.php @@ -2,21 +2,23 @@    <?= partial('partials/header') ?>    <ul class="post-type-icons"> -    <li><a href="/editor"><img src="/images/quill.svg" width="60"></a></li> -    <li><a href="/new"><img src="/images/note.svg" width="60"></a></li> -    <li><a href="/event"><img src="/images/calendar.svg" width="60"></a></li> -    <li><a href="/bookmark"><img src="/images/bookmark.svg" width="60"></a></li> -    <li><a href="/favorite"><img src="/images/star.svg" width="60"></a></li> -    <li><a href="/repost"><img src="/images/repost.svg" width="60"></a></li> -    <li><a href="/itinerary"><img src="/images/plane.svg" width="60"></a></li> -    <li><a href="/email"><img src="/images/email.svg" width="60"></a></li> +    <li><a href="/editor">📄</a></li> +    <li><a href="/new">✏️</a></li> +    <li><a href="/event">📅</a></li> +    <li><a href="/bookmark">🔖</a></li> +    <li><a href="/favorite">👍</a></li> +    <li><a href="/repost">♺</a></li> +    <li><a href="/itinerary">✈️</a></li> +    <li><a href="/review">⭐️</a></li> +    <li><a href="/email">✉️</a></li>    </ul>  </div>  <style type="text/css">  .post-type-icons { -  margin-top: 1em;  +  margin-top: 0;    list-style-type: none; +  font-size: 42pt;  }  .post-type-icons li {    float: left; diff --git a/views/index.php b/views/index.php index 3ed34c2..4e1ffb4 100644 --- a/views/index.php +++ b/views/index.php @@ -5,14 +5,19 @@      <p class="tagline">Quill is a simple app for posting text notes to your website.</p> -    <p>To use Quill, sign in with your domain. Your website will need to support <a href="http://indiewebcamp.com/micropub">Micropub</a> for creating new posts.</p> +    <? if(session('me')): ?> +      <p>You're already signed in!<p> +      <p><a href="/dashboard" class="btn btn-primary">Continue</a></p> +    <? else: ?> +      <p>To use Quill, sign in with your domain. Your website will need to support <a href="https://indieweb.org/micropub">Micropub</a> for creating new posts.</p> -    <form action="/auth/start" method="get" class="form-inline"> -      <input type="url" name="me" placeholder="http://example.com" value="" class="form-control"> -      <input type="submit" value="Sign In" class="btn btn-primary"> -      <input type="hidden" name="client_id" value="https://quill.p3k.io"> -      <input type="hidden" name="redirect_uri" value="https://quill.p3k.io/auth/callback"> -    </form> +      <form action="/auth/start" method="get" class="form-inline"> +        <input type="url" name="me" placeholder="https://example.com" value="" class="form-control" onchange="auto_prefix_url_field(this)" autofocus> +        <input type="submit" value="Sign In" class="btn btn-primary"> +        <input type="hidden" name="client_id" value="https://quill.p3k.io"> +        <input type="hidden" name="redirect_uri" value="https://quill.p3k.io/auth/callback"> +      </form> +    <? endif; ?>    </div> diff --git a/views/new-bookmark.php b/views/new-bookmark.php index 417c86d..1ac208c 100644 --- a/views/new-bookmark.php +++ b/views/new-bookmark.php @@ -13,27 +13,27 @@        <form role="form" style="margin-top: 20px;" id="note_form">          <div class="form-group"> -          <label for="note_bookmark"><code>bookmark-of</code></label> +          <label for="note_bookmark">Bookmark URL (<code>bookmark-of</code>)</label>            <input type="text" id="note_bookmark" value="<?= $this->bookmark_url ?>" class="form-control">          </div>          <div class="form-group"> -          <label for="note_name"><code>name</code></label> +          <label for="note_name">Name (<code>name</code>)</label>            <input type="text" id="note_name" value="<?= $this->bookmark_name ?>" class="form-control">          </div>          <div class="form-group"> -          <label for="note_content"><code>content</code> (optional)</label> +          <label for="note_content">Content (<code>content</code>, optional)</label>            <textarea id="note_content" value="" class="form-control" style="height: 5em;"><?= $this->bookmark_content ?></textarea>          </div>          <div class="form-group"> -          <label for="note_category"><code>category</code> (optional, comma-separated list of tags)</label> +          <label for="note_category">Tags (<code>category</code>, optional, comma-separated list of tags)</label>            <input type="text" id="note_category" value="<?= $this->bookmark_tags ?>" class="form-control" placeholder="e.g. web, personal">          </div>          <div class="form-group"> -          <label for="note_syndicate-to"><code>syndicate-to</code> <a href="javascript:reload_syndications()">(refresh)</a></label> +          <label for="note_syndicate-to">Syndicate (<code>syndicate-to</code>) <a href="javascript:reload_syndications()">refresh</a></label>            <div id="syndication-container">              <?php              if($this->syndication_targets) { diff --git a/views/new-post.php b/views/new-post.php index 0d79767..21571ec 100644 --- a/views/new-post.php +++ b/views/new-post.php @@ -27,6 +27,8 @@          <div class="form-group">            <label for="note_photo"><code>photo</code></label>            <input type="file" name="note_photo" id="note_photo" accept="image/*"> +          <a href="javascript:switchToManualPhotoURL();" id="note_manual_photo">enter photo url</a> +          <a href="javascript:addPhotoURL();" class="hidden" id="add_photo">add photo</a>            <br>            <div id="photo_preview_container" class="hidden">              <img src="" id="photo_preview" style="max-width: 300px; max-height: 300px;"> @@ -180,11 +182,30 @@ function restoreNoteState() {  }  function replacePhotoWithPhotoURL(url) { -  $("#note_photo").after('<input type="url" name="note_photo_url" id="note_photo_url" value="" class="form-control">'); -  $("#note_photo_url").val(url); +  $("#note_photo").after('<input type="url" name="note_photo_url[]" value="" class="note_photo_url form-control">'); +  $(".note_photo_url").val(url);    $("#note_photo").remove();      $("#photo_preview").attr("src", url);    $("#photo_preview_container").removeClass("hidden"); +  $("#note_manual_photo").addClass("hidden"); +} + +function switchToManualPhotoURL() { +  $("#note_photo").after('<input type="url" name="note_photo_url[]" value="" class="note_photo_url form-control">'); +  $("#note_photo").remove();   +  $("#note_photo_url").change(function(){ +    $("#photo_preview").attr("src", $(this).val()); +    $("#photo_preview_container").removeClass("hidden"); +  }); +  $("#note_manual_photo").addClass("hidden"); +  $("#add_photo").removeClass("hidden"); +} + +function addPhotoURL() { +  $(".note_photo_url:last").after('<input type="url" name="note_photo_url[]" value="" class="note_photo_url form-control" style="margin-top:2px;">'); +  if($(".note_photo_url").length == 4) { +    $("#add_photo").remove(); +  }  }  $(function(){ @@ -193,7 +214,7 @@ $(function(){    var hasMediaEndpoint = <?= $this->media_endpoint ? 'true' : 'false' ?>; -  $("#note_content, #note_category, #note_in_reply_to, #note_slug").on('keyup change', function(e){ +  $("#note_content, #note_category, #note_in_reply_to, #note_slug, #note_photo_url").on('keyup change', function(e){      saveNoteState();    }) @@ -233,8 +254,10 @@ $(function(){    });    $("#remove_photo").on("click", function(){      $("#note_photo").val(""); +    $(".note_photo_url").val("");      $("#photo_preview").attr("src", "" );      $("#photo_preview_container").addClass("hidden"); +    saveNoteState();    });    $("#note_content").on('change keyup', function(e){ @@ -307,8 +330,12 @@ $(function(){      // Add either the photo as a file, or the photo URL depending on whether the user has a media endpoint      if(document.getElementById("note_photo") && document.getElementById("note_photo").files[0]) {        formData.append("photo", document.getElementById("note_photo").files[0]); -    } else if($("#note_photo_url").val()) { -      formData.append("photo", $("#note_photo_url").val()); +    } else if($(".note_photo_url").val()) { +      $(".note_photo_url").each(function(){ +        if($(this).val()) { +          formData.append("photo[]", $(this).val()); +        } +      });      }      // Need to append a placeholder field because if the file size max is hit, $_POST will @@ -316,12 +343,11 @@ $(function(){      // This will be stripped by Quill before it's sent to the Micropub endpoint      formData.append("null","null"); -      var request = new XMLHttpRequest();      request.open("POST", "/micropub/multipart");      request.onreadystatechange = function() {        if(request.readyState == XMLHttpRequest.DONE) { -        console.log(request.responseText); +        // console.log(request.responseText);          try {            var response = JSON.parse(request.responseText);            localforage.removeItem('current-note'); diff --git a/views/review.php b/views/review.php new file mode 100644 index 0000000..dacc797 --- /dev/null +++ b/views/review.php @@ -0,0 +1,257 @@ +<div class="narrow"> +  <?= partial('partials/header') ?> + +    <div style="clear: both;"> +      <div class="alert alert-success hidden" id="post_success"><strong>Success! Your post should be on your website now!</strong><br><a href="" id="post_href">View your post</a></div> +      <div class="alert alert-danger hidden" id="post_error"><strong>There was a problem saving your post. Your endpoint did not return a Location header.</strong><br>See <a href="/creating-a-micropub-endpoint">Creating a Micropub Endpoint</a> for more information.</div> +    </div> + +    <form role="form" style="margin-top: 20px;" id="note_form"> + +      <h2>Product</h2> + +      <div class="row"> +        <div class="col-xs-6"> +          <div class="form-group"> +            <label>Name</label> +            <input type="text" class="form-control" id="item_name" placeholder="" value=""> +          </div> +        </div> +        <div class="col-xs-6"> +          <div class="form-group"> +            <label>URL</label> +            <input type="url" class="form-control" id="item_url" placeholder="" value=""> +          </div> +        </div> +      </div> + +      <h2>Review</h2> + +      <div class="rating-stars"> +        <a href="" data-rating="1"></a><a href="" data-rating="2"></a><a href="" data-rating="3"></a><a href="" data-rating="4"></a><a href="" data-rating="5"></a> +        <span class="description">It's okay</span> +      </div> + +      <div class="row review-content hidden"> +        <div class="col-xs-12"> +          <div class="form-group"> +            <textarea id="review_content" value="" class="form-control" style="height: 4em;" placeholder="Write your review here"></textarea>           +            <div id="review-html-note"> +              <input type="checkbox" id="review_is_html" value="1"> Post as HTML +            </div> +          </div> +        </div> +      </div> + +      <div class="row review-summary hidden"> +        <div class="col-xs-12"> +          <div class="form-group"> +            <input id="review_summary" value="" class="form-control" placeholder="Review summary"> +          </div> +        </div> +      </div> + +      <div class="row review-save hidden"> +        <div class="col-xs-12"> +          <div style="float: right; margin-top: 6px;"> +            <button class="btn btn-success" id="btn_post">Post Review</button> +          </div> +        </div> +      </div> + +    </form> + +</div> +<style type="text/css"> +.alert { +  margin-top: 1em; +} +.rating-stars { +  display: flex; +  flex-direction: row; +  align-items: center; +} +.rating-stars .description { +  display: none; +  font-weight: bold; +  margin-left: 20px; +} +.rating-stars .description.visible { +  display: inline-block; +} +.rating-stars a { +  display: inline-block; +  width: 64px; +  height: 64px; +  background-repeat: no-repeat; +  background-image: url("data:image/svg+xml;base64,PHN2ZyB2ZXJzaW9uPSIxLjEiIGlkPSJMYXllcl8xIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHg9IjBweCIgeT0iMHB4IgogICB3aWR0aD0iNjRweCIgaGVpZ2h0PSI2NHB4IiB2aWV3Qm94PSIwIDAgNjQgNjQiIGVuYWJsZS1iYWNrZ3JvdW5kPSJuZXcgMCAwIDY0IDY0IiB4bWw6c3BhY2U9InByZXNlcnZlIj48cG9seWdvbiBmaWxsPSJub25lIiBzdHJva2U9IiNBN0E5QUMiIHN0cm9rZS13aWR0aD0iNCIgc3Ryb2tlLW1pdGVybGltaXQ9IjEwIiBwb2ludHM9IjMxLjg2Niw2LjYxOCA0MC4wOSwyMy4yODEgNTguNDc5LDI1Ljk1MyA0NS4xNzIsMzguOTIzIDQ4LjMxMyw1Ny4yMzkgMzEuODY2LDQ4LjU5MiAxNS40MTgsNTcuMjM5IDE4LjU2LDM4LjkyMyA1LjI1MywyNS45NTMgMjMuNjQyLDIzLjI4MSAiLz48L3N2Zz4="); + +} +.rating-stars a.hover { +  background-image: url("data:image/svg+xml;base64,PHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeD0iMHB4IiB5PSIwcHgiCiAgIHdpZHRoPSI2NHB4IiBoZWlnaHQ9IjY0cHgiIHZpZXdCb3g9IjAgMCA2NCA2NCIgZW5hYmxlLWJhY2tncm91bmQ9Im5ldyAwIDAgNjQgNjQiIHhtbDpzcGFjZT0icHJlc2VydmUiPjxwb2x5Z29uIGZpbGw9IiM1MUFFQ0QiIHN0cm9rZT0iIzUxQUVDRCIgc3Ryb2tlLXdpZHRoPSI0IiBzdHJva2UtbWl0ZXJsaW1pdD0iMTAiIHBvaW50cz0iMzEuODY2LDYuNjE4IDQwLjA5LDIzLjI4MSA1OC40NzksMjUuOTUzIDQ1LjE3MiwzOC45MjMgNDguMzEzLDU3LjIzOSAzMS44NjYsNDguNTkyIDE1LjQxOCw1Ny4yMzkgMTguNTYsMzguOTIzIDUuMjUzLDI1Ljk1MyAyMy42NDIsMjMuMjgxICIvPjwvc3ZnPgo="); +} +.rating-stars a.selected { +  background-image: url("data:image/svg+xml;base64,PHN2ZyB2ZXJzaW9uPSIxLjEiIGlkPSJMYXllcl8xIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHg9IjBweCIgeT0iMHB4IgogICB3aWR0aD0iNjRweCIgaGVpZ2h0PSI2NHB4IiB2aWV3Qm94PSIwIDAgNjQgNjQiIGVuYWJsZS1iYWNrZ3JvdW5kPSJuZXcgMCAwIDY0IDY0IiB4bWw6c3BhY2U9InByZXNlcnZlIj48cG9seWdvbiBmaWxsPSIjRkVDMjBGIiBzdHJva2U9IiNGRUMyMEYiIHN0cm9rZS13aWR0aD0iNCIgc3Ryb2tlLW1pdGVybGltaXQ9IjEwIiBwb2ludHM9IjMxLjg2Niw2LjYxOCA0MC4wOSwyMy4yODEgNTguNDc5LDI1Ljk1MyA0NS4xNzIsMzguOTIzIDQ4LjMxMyw1Ny4yMzkgMzEuODY2LDQ4LjU5MiAxNS40MTgsNTcuMjM5IDE4LjU2LDM4LjkyMyA1LjI1MywyNS45NTMgMjMuNjQyLDIzLjI4MSAiLz48L3N2Zz4="); +} + +.review-content { +  margin-top: 1em; +} +#review-html-note { +  font-size: 12px; +  text-align: right; +} + +</style> +<script> +var selectedRating = 0; +var userSelectedHTML = null; + +function isHTML(str) { +  var doc = new DOMParser().parseFromString(str, "text/html"); +  return Array.from(doc.body.childNodes).some(node => node.nodeType === 1); +} + +function isTouchDevice() { +  return 'ontouchstart' in document.documentElement; +} + +function setSaveButtonState() { +  if(selectedRating > 0 && $("#item_name").val() != "" && $("#item_url").val() != "") { +    $(".review-save").removeClass("hidden"); +  } else { +    $(".review-save").addClass("hidden"); +  } +} + +$(function(){ + +  $(".rating-stars a").on("mouseover",function(){ +    // Disable hover effects on touch devices +    if(isTouchDevice()) { return; } + +    $(this).addClass("hover"); +    var to = intval($(this).data("rating")); +    $(".rating-stars a").removeClass("selected"); +    for(var i=1; i<=to; i++) { +      $(".rating-stars a[data-rating="+i+"]").addClass("hover").removeClass("selected"); +    } +    var description; +    switch(to) { +      case 1: +        description = "I hate it"; break; +      case 2: +        description = "I don't like it"; break; +      case 3: +        description = "It's okay"; break; +      case 4: +        description = "I like it"; break; +      case 5: +        description = "I love it!"; break; +    } +    $(".rating-stars .description").text(description); +    $(".rating-stars span").addClass("visible"); +  }); +  $(".rating-stars a").on("mouseout",function(){ +    $(this).removeClass("hover"); +  }); +  $(".rating-stars").on("mouseout",function(){ +    $(".rating-stars span").removeClass("visible"); +    $(".rating-stars a").removeClass("hover"); +    if(selectedRating) { +      for(var i=1; i<=selectedRating; i++) { +        $(".rating-stars a[data-rating="+i+"]").addClass("selected") +      } +    } +  }); +  $(".rating-stars a").on("click",function(){ +    selectedRating = intval($(this).data("rating")); +    $(".rating-stars a").removeClass("hover").removeClass("selected"); +    for(var i=1; i<=selectedRating; i++) { +      $(".rating-stars a[data-rating="+i+"]").addClass("selected") +    } +    $(".review-content").removeClass("hidden"); +    setSaveButtonState(); +    return false; +  }); + +  $("#review_is_html").on("click", function(){ +    if($(this).attr("checked") == "checked") { +      userSelectedHTML = 1; +    } else { +      userSelectedHTML = -1; +    } +  }); +  $("#review_content").on("keyup", function(){ +    if(userSelectedHTML == null) { +      if(isHTML($(this).val())) { +        $("#review_is_html").attr("checked", "checked"); +      } else { +        $("#review_is_html").removeAttr("checked"); +      } +    } +    if($(this).val() != "") { +      $(".review-summary").removeClass("hidden"); +    } else { +      $(".review-summary").addClass("hidden"); +    } + +    var scrollHeight = document.getElementById("review_content").scrollHeight; +    var currentHeight = parseInt($("#review_content").css("height")); +    if(Math.abs(scrollHeight - currentHeight) > 20) { +      $("#review_content").css("height", (scrollHeight+30)+"px"); +    } +  }); + +  $("#item_name").on("keyup", setSaveButtonState); +  $("#item_url").on("keyup", setSaveButtonState); + +  $("#btn_post").click(function(){ +    $("#btn_post").addClass("loading disabled").text("Working..."); + +    var review = { +      item: [{ +              type: "h-product", +              properties: { +                name: [$("#item_name").val()], +                url: [$("#item_url").val()] +              } +            }], +      rating: [selectedRating], +    }; +    if($("#review_content").val() != "") { +      if($("#review_is_html").attr("checked") == "checked") { +        review["content"] = [{html: $("#review_content").val()}]; +      } else { +        review["content"] = [$("#review_content").val()]; +      } +    } +    if($("#review_summary").val() != "") { +      review["summary"] = [$("#review_summary").val()]; +    } + +    $.post("/micropub/postjson", { +      data: JSON.stringify({ +        "type": "h-review", +        "properties": review +      }) +    }, function(response){ +      $("#btn_post").removeClass("loading disabled").text("Post Review"); + +      if(response.location != false) { +        $("#post_success").removeClass('hidden'); +        $("#post_error").addClass('hidden'); +        $("#post_href").attr("href", response.location); +        $("#note_form").addClass("hidden"); +      } else { +        $("#post_success").addClass('hidden'); +        $("#post_error").removeClass('hidden'); +      } + +    }); +    return false; + +  }); + +}); +</script> | 
