Преглед изворни кода

Pagination and dynamic routes, individual post view, templated 404

George S. Baugh пре 5 година
родитељ
комит
47858af31e

+ 16 - 0
lib/Trog/Data/DUMMY.pm

@@ -82,6 +82,19 @@ my $example_posts = [
         version      => 0,
         preview      => '/img/avatar/humm.gif',
     },
+    { 
+        content_type => "image/jpeg",
+        data         => "Test Pattern",
+        href         => "/img/sys/testpattern.jpg",
+        local_href   => "/img/sys/testpattern.jpg",
+        title        => "testpattern.jpg",
+        user         => 'Nobody',
+        id           => 90210,
+        tags         => ['image', 'files'],
+        created      => time(),
+        version      => 0,
+        preview      => '/img/sys/testpattern.jpg',
+    },
     {
         content_type => "audio/mpeg",
         data         => "Test recording for tCMS",
@@ -133,6 +146,9 @@ sub new ($class, $config) {
 sub get ($self, %request) {
     my @filtered = @$example_posts;
 
+    # If an ID is passed, just get that
+    @filtered = grep { $_->{id} eq $request{id} } @filtered if $request{id};
+
     # First, paginate
     my $offset = int($request{limit});
     $offset = @filtered < $offset ? @filtered : $offset;

+ 45 - 2
lib/Trog/Routes/HTML.pm

@@ -68,6 +68,13 @@ our %routes = (
         auth     => 1,
         callback => \&Trog::Routes::HTML::post,
     },
+    '/posts/(.*)' => {
+        method   => 'GET',
+        auth     => 1,
+        callback => \&Trog::Routes::HTML::posts,
+        captures => ['id'],
+    },
+
     '/posts' => {
         method   => 'GET',
         callback => \&Trog::Routes::HTML::posts,
@@ -86,7 +93,11 @@ our %routes = (
 
 # Build aliases for /post with extra data
 my @post_aliases = qw{news blog image video audio about files};
-@routes{map { "/$_" } @post_aliases} = map { my %copy = %{$routes{'/posts'}}; $copy{data} = { tag => [$_] }; \%copy } @post_aliases;
+@routes{map { "/$_" } @post_aliases} = map { my %copy = %{$routes{'/posts'}}; $copy{data}{tag} = [$_]; \%copy } @post_aliases;
+
+# Build aliases for /post/(.*) with extra data
+@routes{map { "/$_/(.*)" } @post_aliases} = map { my %copy = %{$routes{'/posts/(.*)'}}; \%copy } @post_aliases;
+
 
 # Grab theme routes
 my $themed = 0;
@@ -127,6 +138,7 @@ sub index ($query, $input, $render_cb, $content = '', $i_styles = []) {
     my $search_info = Trog::Data->new($conf);
 
     return $render_cb->('index.tx',{
+        code        => $query->{code},
         user        => $query->{user},
         search_lang => $search_info->{lang},
         search_help => $search_info->{help},
@@ -143,6 +155,33 @@ sub index ($query, $input, $render_cb, $content = '', $i_styles = []) {
     });
 }
 
+=head1 ADMIN ROUTES
+
+These are things that issue returns other than 200, and are not directly accessible by users via any defined route.
+
+=cut
+
+sub notfound ($query,$input,$render_cb) {
+    $query->{code} = 404;
+
+    my $processor = Text::Xslate->new(
+        path   => _dir_for_resource('notfound.tx'),
+    );
+
+    my $styles = _build_themed_styles('notfound.css');
+    my $content = $processor->render('notfound.tx', {
+        title    => "Return to Sender, Address unknown",
+        route    => $query->{route},
+    });
+    return Trog::Routes::HTML::index($query, $input, $render_cb, $content, $styles);
+}
+
+=head1 NORMAL ROUTES
+
+These are expected to either return a 200, or redirect to something which does.
+
+=cut
+
 sub setup ($query, $input, $render_cb) {
     File::Touch::touch("$ENV{HOME}/.tcms/setup");
     return $render_cb->('notconfigured.tx', {
@@ -319,10 +358,12 @@ sub posts ($query, $input, $render_cb) {
     my $tags = _coerce_array($query->{tag});
     my $posts = _post_helper($query, $tags);
 
+    return notfound($query,$input,$render_cb) unless @$posts;
+
     my $fmt = $query->{format} || '';
     return _rss($posts) if $fmt eq 'rss';
 
-    my $processor ||= Text::Xslate->new(
+    my $processor = Text::Xslate->new(
         path   => _dir_for_resource('posts.tx'),
     );
 
@@ -335,6 +376,7 @@ sub posts ($query, $input, $render_cb) {
         page     => int($query->{page} || 1),
         limit    => int($query->{limit} || 1),
         sizes    => [25,50,100],
+        rss      => !$query->{id},
         category => $themed ? Theme::path_to_tile($query->{route}) : $query->{route},
     });
     return Trog::Routes::HTML::index($query, $input, $render_cb, $content, $styles);
@@ -347,6 +389,7 @@ sub _post_helper ($query, $tags) {
         limit => int($query->{limit} || 25),
         tags  => $tags,
         like  => $query->{like},
+        id    => $query->{id},
     );
 }
 

+ 20 - 2
www/server.psgi

@@ -84,8 +84,24 @@ my $app = sub {
     # If it's just a file, serve it up
     return _serve("www/$path", $last_fetch) if -f "www/$path";
 
+    #Handle regex/capture routes
+    if (!exists $routes{$path}) {
+        my @captures;
+        foreach my $pattern (keys(%routes)) {
+            @captures = $path =~ m/^$pattern$/;
+            if (@captures) {
+                $path = $pattern;
+                foreach my $field (@{$routes{$path}{captures}}) {
+                    $routes{$path}{data} //= {};
+                    $routes{$path}{data}{$field} = shift @captures;
+                }
+                last;
+            }
+        }
+    }
+
     #TODO reject inappropriate content-lengths
-    return [ 404, [$content_types{plain}], ["RETURN TO SENDER"]] unless exists $routes{$path};
+    return Trog::Routes::HTML::notfound($query,$env->{'psgi.input'}, \&_render) unless exists $routes{$path};
     return [ 400, [$content_types{plain}], ["BAD REQUEST"]] unless $routes{$path}{method} eq $env->{REQUEST_METHOD};
 
     @{$query}{keys(%{$routes{$path}{'data'}})} = values(%{$routes{$path}{'data'}}) if ref $routes{$path}{'data'} eq 'HASH' && %{$routes{$path}{'data'}};
@@ -149,13 +165,15 @@ sub _render ($template, $vars, @headers) {
     $vars->{contenttype} //= $content_types{html};
     $vars->{cachecontrol} //= $cache_control{revalidate};
 
+    $vars->{code} ||= 200;
+
     push(@headers, $vars->{contenttype});
     push(@headers,$vars->{contentdisposition}) if $vars->{contentdisposition};
     push(@headers, $vars->{cachecontrol}) if $vars->{cachecontrol};
     my $h = join("\n",@headers);
 
     my $body = $processor->render($template,$vars);
-    return [200, [$h], [encode_utf8($body)]];
+    return [$vars->{code}, [$h], [encode_utf8($body)]];
 }
 
 

+ 4 - 0
www/styles/screen.css

@@ -288,3 +288,7 @@ p.posteditortitle {
     border: .25rem dashed black;
     padding: .5rem;
 }
+
+.undecorated {
+    text-decoration: none;
+}

+ 3 - 0
www/templates/notfound.tx

@@ -0,0 +1,3 @@
+404 not found
+<br /><br />
+No such resource <: $path :>

+ 16 - 0
www/templates/paginator.tx

@@ -0,0 +1,16 @@
+Page <: $page :>
+<div style="float:right;">
+    <a href="?page=<: $page + 1 :>&limit=<: $limit :>">Next</a>
+    : if ( $page > 1 ) {
+    <a href="?page=<: $page - 1 :>&limit=<: $limit :>">Prev</a>
+    : }
+    Size:
+    <form style="display:inline;">
+        <input type="hidden" name="page"  value="<: $page :>" />
+        <select name="limit" class="coolbutton">
+            : for $sizes -> $size {
+            <option value="<: $size :>"><: $size :></option>
+            : }
+        </select>
+    </form>
+</div>

+ 6 - 1
www/templates/posts.tx

@@ -1,11 +1,14 @@
 <p class="title">
+: if ( $rss ) {
 <a title="RSS" class="rss" href="<: $route :>?format=rss"></a>
 <: $category :>:
+: }
 </p>
 : for $posts -> $post {
     <hr>
     <h3 class='blogtitles'>
-        <a href='<: $post.href :>'><: $post.title :></a>
+        <a href='/posts/<: $post.id :>'><: $post.title :></a>
+        <a class="undecorated" href='<: $post.href :>'>🔗</a>
         <span id="<: $post.id :>-time" style="float:right;"><: $post.created :></span>
         <a class='usericon <: $post.user :>' title='Posted by <: $post.user :>'></a>
     </h3>
@@ -45,5 +48,7 @@
         });
     </script>
 : }
+: if ( $rss ) {
 <hr />
 : include "paginator.tx";
+: }