Remove Dancer2 files
This commit is contained in:
parent
ff22b71fa1
commit
fbcaea8bfc
1
TODO
1
TODO
@ -1,7 +1,6 @@
|
|||||||
- thumbnailer / caching component
|
- thumbnailer / caching component
|
||||||
- query media by hash
|
- query media by hash
|
||||||
- media upload
|
- media upload
|
||||||
- consider mojolicious
|
|
||||||
- account registration
|
- account registration
|
||||||
- use email for confirmation and reset, but don't store email; instead
|
- use email for confirmation and reset, but don't store email; instead
|
||||||
store a crypt(3) of it
|
store a crypt(3) of it
|
||||||
|
15
bin/app.psgi
15
bin/app.psgi
@ -1,15 +0,0 @@
|
|||||||
#!/usr/bin/env perl
|
|
||||||
|
|
||||||
use strict;
|
|
||||||
use warnings;
|
|
||||||
use FindBin;
|
|
||||||
use lib "$FindBin::Bin/../lib";
|
|
||||||
|
|
||||||
use PoorBooru;
|
|
||||||
use PoorBooru::API::V0;
|
|
||||||
use Plack::Builder;
|
|
||||||
|
|
||||||
builder {
|
|
||||||
mount "/" => PoorBooru->to_app;
|
|
||||||
mount "/api/v0" => PoorBooru::API::V0->to_app;
|
|
||||||
};
|
|
68
config.yml
68
config.yml
@ -1,68 +0,0 @@
|
|||||||
# This is the main configuration file of your Dancer2 app
|
|
||||||
# env-related settings should go to environments/$env.yml
|
|
||||||
# all the settings in this file will be loaded at Dancer's startup.
|
|
||||||
|
|
||||||
# === Basic configuration ===
|
|
||||||
|
|
||||||
# Your application's name
|
|
||||||
appname: "PoorBooru"
|
|
||||||
|
|
||||||
# The default layout to use for your application (located in
|
|
||||||
# views/layouts/main.tt)
|
|
||||||
layout: "main"
|
|
||||||
|
|
||||||
# when the charset is set to UTF-8 Dancer2 will handle for you
|
|
||||||
# all the magic of encoding and decoding. You should not care
|
|
||||||
# about unicode within your app when this setting is set (recommended).
|
|
||||||
charset: "UTF-8"
|
|
||||||
|
|
||||||
# === Engines ===
|
|
||||||
#
|
|
||||||
# NOTE: All the engine configurations need to be under a single "engines:"
|
|
||||||
# key. If you uncomment engine configurations below, make sure to delete
|
|
||||||
# all "engines:" lines except the first. Otherwise, only the last
|
|
||||||
# "engines:" block will take effect.
|
|
||||||
|
|
||||||
# template engine
|
|
||||||
# simple: default and very basic template engine
|
|
||||||
# template_toolkit: TT
|
|
||||||
|
|
||||||
template: "tiny"
|
|
||||||
|
|
||||||
# template: "template_toolkit"
|
|
||||||
# engines:
|
|
||||||
# template:
|
|
||||||
# template_toolkit:
|
|
||||||
# # Note: start_tag and end_tag are regexes
|
|
||||||
# start_tag: '<%'
|
|
||||||
# end_tag: '%>'
|
|
||||||
|
|
||||||
# session engine
|
|
||||||
#
|
|
||||||
# Simple: in-memory session store - Dancer2::Session::Simple
|
|
||||||
# YAML: session stored in YAML files - Dancer2::Session::YAML
|
|
||||||
#
|
|
||||||
# Check out metacpan for other session storage options:
|
|
||||||
# https://metacpan.org/search?q=Dancer2%3A%3ASession&search_type=modules
|
|
||||||
#
|
|
||||||
# Default value for 'cookie_name' is 'dancer.session'. If you run multiple
|
|
||||||
# Dancer apps on the same host then you will need to make sure 'cookie_name'
|
|
||||||
# is different for each app.
|
|
||||||
#
|
|
||||||
#engines:
|
|
||||||
# session:
|
|
||||||
# Simple:
|
|
||||||
# cookie_name: testapp.session
|
|
||||||
#
|
|
||||||
#engines:
|
|
||||||
# session:
|
|
||||||
# YAML:
|
|
||||||
# cookie_name: eshop.session
|
|
||||||
# is_secure: 1
|
|
||||||
# is_http_only: 1
|
|
||||||
|
|
||||||
plugins:
|
|
||||||
DBIC:
|
|
||||||
default:
|
|
||||||
dsn: "dbi:SQLite:dbname=db/PoorBooru.db"
|
|
||||||
schema_class: PoorBooru::Schema
|
|
34
cpanfile
34
cpanfile
@ -1,34 +0,0 @@
|
|||||||
requires "Dancer2" => "0.400000";
|
|
||||||
|
|
||||||
recommends "YAML" => "0";
|
|
||||||
recommends "URL::Encode::XS" => "0";
|
|
||||||
recommends "CGI::Deurl::XS" => "0";
|
|
||||||
recommends "CBOR::XS" => "0";
|
|
||||||
recommends "YAML::XS" => "0";
|
|
||||||
recommends "Class::XSAccessor" => "0";
|
|
||||||
recommends "Crypt::URandom" => "0";
|
|
||||||
recommends "HTTP::XSCookies" => "0";
|
|
||||||
recommends "HTTP::XSHeaders" => "0";
|
|
||||||
recommends "Math::Random::ISAAC::XS" => "0";
|
|
||||||
recommends "MooX::TypeTiny" => "0";
|
|
||||||
recommends "Type::Tiny::XS" => "0";
|
|
||||||
|
|
||||||
feature 'accelerate', 'Accelerate Dancer2 app performance with XS modules' => sub {
|
|
||||||
requires "URL::Encode::XS" => "0";
|
|
||||||
requires "CGI::Deurl::XS" => "0";
|
|
||||||
requires "YAML::XS" => "0";
|
|
||||||
requires "Class::XSAccessor" => "0";
|
|
||||||
requires "Cpanel::JSON::XS" => "0";
|
|
||||||
requires "Crypt::URandom" => "0";
|
|
||||||
requires "HTTP::XSCookies" => "0";
|
|
||||||
requires "HTTP::XSHeaders" => "0";
|
|
||||||
requires "Math::Random::ISAAC::XS" => "0";
|
|
||||||
requires "MooX::TypeTiny" => "0";
|
|
||||||
requires "Type::Tiny::XS" => "0";
|
|
||||||
};
|
|
||||||
|
|
||||||
on "test" => sub {
|
|
||||||
requires "Test::More" => "0";
|
|
||||||
requires "HTTP::Request::Common" => "0";
|
|
||||||
};
|
|
||||||
|
|
@ -1,22 +0,0 @@
|
|||||||
# configuration file for development environment
|
|
||||||
|
|
||||||
# the logger engine to use
|
|
||||||
# console: log messages to STDOUT (your console where you started the
|
|
||||||
# application server)
|
|
||||||
# file: log message to a file in log/
|
|
||||||
logger: "console"
|
|
||||||
|
|
||||||
# the log level for this environment
|
|
||||||
# core is the lowest, it shows Dancer2's core log messages as well as yours
|
|
||||||
# (debug, info, warning and error)
|
|
||||||
log: "core"
|
|
||||||
|
|
||||||
# should Dancer2 show a stacktrace when an 5xx error is caught?
|
|
||||||
# if set to yes, public/500.html will be ignored and either
|
|
||||||
# views/500.tt, 'error_template' template, or a default error template will be used.
|
|
||||||
show_errors: 1
|
|
||||||
|
|
||||||
# print the banner
|
|
||||||
startup_info: 1
|
|
||||||
|
|
||||||
poorbooru_api: "http://localhost:8080/api/v0"
|
|
@ -1,13 +0,0 @@
|
|||||||
# configuration file for production environment
|
|
||||||
|
|
||||||
# only log warning and error messsages
|
|
||||||
log: "warning"
|
|
||||||
|
|
||||||
# log message to a file in logs/
|
|
||||||
logger: "file"
|
|
||||||
|
|
||||||
# hide errors
|
|
||||||
show_errors: 0
|
|
||||||
|
|
||||||
# disable server tokens in production environments
|
|
||||||
no_server_tokens: 1
|
|
169
lib/PoorBooru.pm
169
lib/PoorBooru.pm
@ -1,169 +0,0 @@
|
|||||||
package PoorBooru;
|
|
||||||
use v5.32;
|
|
||||||
use Dancer2;
|
|
||||||
use HTTP::Tiny;
|
|
||||||
|
|
||||||
|
|
||||||
our $VERSION = v0.0.1;
|
|
||||||
|
|
||||||
my $POORBOORU_API = setting("poorbooru_api");
|
|
||||||
|
|
||||||
|
|
||||||
sub validate_page_number ($) { $_[0] =~ /^[1-9][0-9]*$/ }
|
|
||||||
|
|
||||||
sub api_request ($$;%)
|
|
||||||
{
|
|
||||||
my ($method, $path, $params_hashref) = @_;
|
|
||||||
|
|
||||||
my $http = HTTP::Tiny->new(
|
|
||||||
timeout => 15,
|
|
||||||
verify_SSL => true,
|
|
||||||
agent => setting("appname") . " backend " .
|
|
||||||
version::->parse($VERSION)->normal,
|
|
||||||
|
|
||||||
default_headers => {
|
|
||||||
"Accept" => "application/json",
|
|
||||||
"Content-Type" => "application/json",
|
|
||||||
},
|
|
||||||
);
|
|
||||||
my $params = $http->www_form_urlencode($params_hashref // {});
|
|
||||||
|
|
||||||
return $http->request($method, "$POORBOORU_API$path" .
|
|
||||||
($params ne "" ? "?$params" : ""));
|
|
||||||
}
|
|
||||||
|
|
||||||
sub api_get ($;%) { api_request("GET", shift, @_) }
|
|
||||||
|
|
||||||
sub nav_pager ($$)
|
|
||||||
{
|
|
||||||
my ($p, $uri) = @_;
|
|
||||||
|
|
||||||
my (%pager, %res);
|
|
||||||
%pager = %$p;
|
|
||||||
$res{first} = $pager{first} != $pager{cur} ?
|
|
||||||
uri_for($uri, { page => $pager{first} }) : undef;
|
|
||||||
$res{prev} = defined($pager{prev}) && $pager{prev} != $pager{first} ?
|
|
||||||
uri_for($uri, { page => $pager{prev} }) : undef;
|
|
||||||
$res{cur} = $pager{cur};
|
|
||||||
$res{next} = defined($pager{next}) && $pager{next} != $pager{last} ?
|
|
||||||
uri_for($uri, { page => $pager{next} }) : undef;
|
|
||||||
$res{last} = $pager{last} != $pager{cur} ?
|
|
||||||
uri_for($uri, { page => $pager{last} }) : undef;
|
|
||||||
|
|
||||||
return \%res;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
hook before_template_render => sub {
|
|
||||||
my $tokens = shift;
|
|
||||||
|
|
||||||
$tokens->{uris}->{root} = uri_for("/");
|
|
||||||
$tokens->{uris}->{login} = uri_for("/login");
|
|
||||||
$tokens->{uris}->{logout} = uri_for("/logout");
|
|
||||||
$tokens->{uris}->{random} = uri_for("/random/media");
|
|
||||||
$tokens->{uris}->{tags} = uri_for("/tags");
|
|
||||||
};
|
|
||||||
|
|
||||||
get "/" => sub {
|
|
||||||
my $page = query_parameters->get("page") // 1;
|
|
||||||
send_error("Invalid page number", 400) if !validate_page_number($page);
|
|
||||||
|
|
||||||
my $res = api_get("/media", { page => $page });
|
|
||||||
send_error("API error", 500) if !$res->{success};
|
|
||||||
|
|
||||||
my $data = decode_json($res->{content});
|
|
||||||
my @media = map +{
|
|
||||||
# XXX point to a cache
|
|
||||||
image_src => "$POORBOORU_API/download/$_",
|
|
||||||
media_uri => uri_for("/media/$_"),
|
|
||||||
}, @{$data->{media}};
|
|
||||||
|
|
||||||
template "gallery" => {
|
|
||||||
title => "PoorBooru",
|
|
||||||
media => \@media,
|
|
||||||
pager => nav_pager($data->{pager}, "/"),
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
get "/tags" => sub {
|
|
||||||
my $page = query_parameters->get("page") // 1;
|
|
||||||
send_error("Invalid page number", 400) if !validate_page_number($page);
|
|
||||||
|
|
||||||
my $res = api_get("/tags", { page => $page });
|
|
||||||
send_error("API error", 500) if !$res->{success};
|
|
||||||
|
|
||||||
my $data = decode_json($res->{content});
|
|
||||||
my @tags = map +{
|
|
||||||
name => $_->{name},
|
|
||||||
count => $_->{count},
|
|
||||||
uri => uri_for("/tag/$_->{name}"),
|
|
||||||
}, @{$data->{tags}};
|
|
||||||
|
|
||||||
template "tags" => {
|
|
||||||
title => "Tags",
|
|
||||||
tags => \@tags,
|
|
||||||
pager => nav_pager($data->{pager}, "/tags"),
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
get "/tag/:tag_id_or_name" => sub {
|
|
||||||
my $page = query_parameters->get("page") // 1;
|
|
||||||
send_error("Invalid page number", 400) if !validate_page_number($page);
|
|
||||||
|
|
||||||
my $tag_id_or_name = route_parameters->get("tag_id_or_name");
|
|
||||||
|
|
||||||
my $res = api_get("/tag/$tag_id_or_name", { page => $page });
|
|
||||||
send_error("API error", 500) if !$res->{success};
|
|
||||||
|
|
||||||
my $data = decode_json($res->{content});
|
|
||||||
my @media = map +{
|
|
||||||
# XXX point to a cache
|
|
||||||
image_src => "$POORBOORU_API/download/$_",
|
|
||||||
media_uri => uri_for("/media/$_"),
|
|
||||||
}, @{$data->{media}};
|
|
||||||
|
|
||||||
template "gallery" => {
|
|
||||||
title => $data->{name},
|
|
||||||
media => \@media,
|
|
||||||
pager => nav_pager($data->{pager}, "/tag/$tag_id_or_name"),
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
get "/media/:media_id" => sub {
|
|
||||||
my $media_id = route_parameters->get("media_id");
|
|
||||||
|
|
||||||
my $res = api_get("/media/$media_id");
|
|
||||||
send_error("API error", 500) if !$res->{success};
|
|
||||||
|
|
||||||
my $data = decode_json($res->{content});
|
|
||||||
|
|
||||||
template "media" => {
|
|
||||||
no_title => 1,
|
|
||||||
title => $data->{id},
|
|
||||||
image_src => "$POORBOORU_API/download/$data->{id}",
|
|
||||||
media_id => $data->{id},
|
|
||||||
media_filename => $data->{filename},
|
|
||||||
media_size => $data->{size},
|
|
||||||
media_tags => [],
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
get "/random/media" => sub {
|
|
||||||
my $res = api_get("/random/media");
|
|
||||||
send_error("API error", 500) if !$res->{success};
|
|
||||||
|
|
||||||
my $data = decode_json($res->{content});
|
|
||||||
# XXX body content.
|
|
||||||
redirect "/media/$data->{id}";
|
|
||||||
};
|
|
||||||
|
|
||||||
get "/random/tag" => sub {
|
|
||||||
my $res = api_get("/random/tag");
|
|
||||||
send_error("API error", 500) if !$res->{success};
|
|
||||||
|
|
||||||
my $data = decode_json($res->{content});
|
|
||||||
# XXX body content.
|
|
||||||
redirect "/tag/$data->{name}";
|
|
||||||
};
|
|
||||||
|
|
||||||
true;
|
|
@ -1,176 +0,0 @@
|
|||||||
package PoorBooru::API::V0;
|
|
||||||
use v5.32;
|
|
||||||
use Dancer2;
|
|
||||||
use Dancer2::Plugin::DBIC;
|
|
||||||
|
|
||||||
|
|
||||||
use constant {
|
|
||||||
DEFAULT_CONTENT_TYPE => "application/json",
|
|
||||||
};
|
|
||||||
|
|
||||||
our $VERSION = v0.0.1;
|
|
||||||
|
|
||||||
my $MEDIA_SEARCH_OPTS = {
|
|
||||||
order_by => { -desc => "media_id" },
|
|
||||||
rows => 20,
|
|
||||||
};
|
|
||||||
my $TAGS_COUNT_VIEW_SEARCH_OPTS = {
|
|
||||||
order_by => { -desc => "count" },
|
|
||||||
rows => 100,
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
sub validate_page_number ($) { $_[0] =~ /^[1-9][0-9]*$/ }
|
|
||||||
sub validate_tag_name ($) { $_[0] !~ /^[1-9][0-9]*$/ && $_[0] =~ /^\w+$/ }
|
|
||||||
|
|
||||||
sub mkpager ($)
|
|
||||||
{
|
|
||||||
my $pager = $_[0]->pager;
|
|
||||||
return {
|
|
||||||
first => $pager->first_page,
|
|
||||||
prev => $pager->previous_page,
|
|
||||||
cur => $pager->current_page,
|
|
||||||
next => $pager->next_page,
|
|
||||||
last => $pager->last_page,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
set serializer => "JSON";
|
|
||||||
|
|
||||||
|
|
||||||
get "/meta" => sub {
|
|
||||||
return [
|
|
||||||
{ path => "/meta", verb => "GET" },
|
|
||||||
{ path => "/tags", verb => "GET" },
|
|
||||||
{ path => "/tags", verb => "POST" },
|
|
||||||
{ path => "/tag/:tag_id_or_name", verb => "GET" },
|
|
||||||
{ path => "/media/:media_id", verb => "GET" },
|
|
||||||
{ path => "/download/:media_id", verb => "GET" },
|
|
||||||
{ path => "/random", verb => "GET" },
|
|
||||||
],
|
|
||||||
};
|
|
||||||
|
|
||||||
get "/tags" => sub {
|
|
||||||
my $page = query_parameters->get("page") // 1;
|
|
||||||
send_error("Invalid page number", 400) if !validate_page_number($page);
|
|
||||||
|
|
||||||
my $paged_tags = schema("default")->resultset("TagsCountView")
|
|
||||||
->search({}, $TAGS_COUNT_VIEW_SEARCH_OPTS)->page($page);
|
|
||||||
my @tags = map +{
|
|
||||||
id => $_->tag_id,
|
|
||||||
name => $_->name,
|
|
||||||
count => $_->count,
|
|
||||||
}, $paged_tags->all;
|
|
||||||
|
|
||||||
return {
|
|
||||||
pager => mkpager($paged_tags),
|
|
||||||
tags => \@tags,
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
post "/tags" => sub {
|
|
||||||
my @tag_names = body_parameters->get_all("name");
|
|
||||||
send_error("No tags provided", 400) if @tag_names == 0;
|
|
||||||
send_error("Too many tags provided", 400) if @tag_names > 100;
|
|
||||||
send_error("Invalid tag names", 400) if
|
|
||||||
grep { validate_tag_name($_) } @tag_names;
|
|
||||||
|
|
||||||
my @tags;
|
|
||||||
eval {
|
|
||||||
@tags = map +{
|
|
||||||
id => $_->tag_id,
|
|
||||||
name => $_->name,
|
|
||||||
}, schema("default")->resultset("Tag")
|
|
||||||
->populate([map +{ name => $_ }, @tag_names]);
|
|
||||||
} or send_error("Tag exists", 409);
|
|
||||||
|
|
||||||
return \@tags;
|
|
||||||
};
|
|
||||||
|
|
||||||
get "/tag/:tag_id_or_name" => sub {
|
|
||||||
my $page = query_parameters->get("page") // 1;
|
|
||||||
send_error("Invalid page number", 400) if !validate_page_number($page);
|
|
||||||
|
|
||||||
my $tag_id_or_name = route_parameters->get("tag_id_or_name");
|
|
||||||
|
|
||||||
my $query = [
|
|
||||||
{ tag_id => $tag_id_or_name },
|
|
||||||
{ name => $tag_id_or_name },
|
|
||||||
];
|
|
||||||
my $tag = schema("default")->resultset("Tag")->single($query);
|
|
||||||
send_error("Tag not found", 404) if !defined($tag);
|
|
||||||
|
|
||||||
my $paged_media = schema("default")->resultset("MediaTag")
|
|
||||||
->search({ tag_id => $tag->tag_id }, $MEDIA_SEARCH_OPTS)
|
|
||||||
->page($page);
|
|
||||||
my @media = map { $_->media_id } $paged_media->all;
|
|
||||||
|
|
||||||
return {
|
|
||||||
id => $tag->tag_id,
|
|
||||||
name => $tag->name,
|
|
||||||
pager => mkpager($paged_media),
|
|
||||||
media => \@media,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
get "/random/tag" => sub {
|
|
||||||
my $tag = schema("default")->resultset("Tag")
|
|
||||||
->search({}, { order_by => \"random()", limit => 1 })->single;
|
|
||||||
|
|
||||||
send_error("Tag not found", 404) if !defined($tag);
|
|
||||||
|
|
||||||
forward "/tag/" . $tag->tag_id;
|
|
||||||
};
|
|
||||||
|
|
||||||
get "/media" => sub {
|
|
||||||
my $page = query_parameters->get("page") // 1;
|
|
||||||
send_error("Invalid page number", 400) if !validate_page_number($page);
|
|
||||||
|
|
||||||
my $paged_media = schema("default")->resultset("Media")
|
|
||||||
->search({}, $MEDIA_SEARCH_OPTS)->page($page);
|
|
||||||
my @media = map { $_->media_id } $paged_media->all;
|
|
||||||
|
|
||||||
return {
|
|
||||||
pager => mkpager($paged_media),
|
|
||||||
media => \@media,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
get "/media/:media_id" => sub {
|
|
||||||
my $media = schema("default")->resultset("Media")
|
|
||||||
->single({ media_id => route_parameters->get("media_id") });
|
|
||||||
|
|
||||||
send_error("Media not found", 404) if !defined($media);
|
|
||||||
|
|
||||||
return {
|
|
||||||
id => $media->media_id,
|
|
||||||
filename => $media->filename,
|
|
||||||
size => length($media->content),
|
|
||||||
download_path => "/download/" . $media->media_id,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
get "/download/:media_id" => sub {
|
|
||||||
my $media = schema("default")->resultset("Media")
|
|
||||||
->single({ media_id => route_parameters->get("media_id") });
|
|
||||||
|
|
||||||
send_error("Media not found", 404) if !defined($media);
|
|
||||||
|
|
||||||
send_file(
|
|
||||||
\$media->content,
|
|
||||||
content_type => $media->content_type // DEFAULT_CONTENT_TYPE,
|
|
||||||
filename => $media->filename,
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
get "/random/media" => sub {
|
|
||||||
my $media = schema("default")->resultset("Media")
|
|
||||||
->search({}, { order_by => \"random()", limit => 1 })->single;
|
|
||||||
|
|
||||||
send_error("Media not found", 404) if !defined($media);
|
|
||||||
|
|
||||||
forward "/media/" . $media->media_id;
|
|
||||||
};
|
|
||||||
|
|
||||||
true;
|
|
@ -1,7 +0,0 @@
|
|||||||
<div class="flex-c-horizontal flex-c-wrap gallery">
|
|
||||||
[% FOREACH entry IN media -%]
|
|
||||||
<a href="[% entry.media_uri %]">
|
|
||||||
<img class="gallery-image" src="[% entry.image_src %]" />
|
|
||||||
</a>
|
|
||||||
[% END -%]
|
|
||||||
</div>
|
|
@ -1,50 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="[% settings.charset %]">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=yes">
|
|
||||||
<title>[% settings.appname %] - [% title %]</title>
|
|
||||||
<link rel="stylesheet" href="[% request.uri_base %]/css/style.css">
|
|
||||||
</head>
|
|
||||||
<body class="bg-default fg-default flex-c-vertical">
|
|
||||||
<header>
|
|
||||||
<nav class="viewport flex-c-horizontal main-nav-link-gap">
|
|
||||||
<a class="main-nav-link" href="[% uris.root %]">[% settings.appname %]</a>
|
|
||||||
<a class="main-nav-link" href="[% uris.random %]">random</a>
|
|
||||||
<a class="main-nav-link" href="[% uris.tags %]">tags</a>
|
|
||||||
<span class="flex-i-fullsize"><!-- spacer --></span>
|
|
||||||
<a class="main-nav-link" href="[% uris.login %]">login</a>
|
|
||||||
</nav>
|
|
||||||
</header>
|
|
||||||
<main class="border-bottom border-top border-accent flex-i-fullsize flex-c-vertical">
|
|
||||||
<div class="viewport flex-i-fullsize flex-c-vertical">
|
|
||||||
[% UNLESS no_title %]
|
|
||||||
<h1>[% title %]</h1>
|
|
||||||
[% END %]
|
|
||||||
<div class="viewport flex-i-fullsize">[% content %]</div>
|
|
||||||
[% IF pager %]
|
|
||||||
<footer>
|
|
||||||
<nav class="text-center">
|
|
||||||
[% IF pager.first -%]
|
|
||||||
<a href="[% pager.first %]"><<</a>
|
|
||||||
[% END -%]
|
|
||||||
[% IF pager.prev -%]
|
|
||||||
<a href="[% pager.prev %]"><</a>
|
|
||||||
[% END -%]
|
|
||||||
<span>[% pager.cur %]</span>
|
|
||||||
[% IF pager.next -%]
|
|
||||||
<a href="[% pager.next %]">></a>
|
|
||||||
[% END -%]
|
|
||||||
[% IF pager.last -%]
|
|
||||||
<a href="[% pager.last %]">>></a>
|
|
||||||
[% END -%]
|
|
||||||
</nav>
|
|
||||||
</footer>
|
|
||||||
[% END %]
|
|
||||||
</div>
|
|
||||||
</main>
|
|
||||||
<footer class="text-center">
|
|
||||||
<p class="viewport text-small">Powered by <a href="https://www.openbsd.org">OpenBSD</a> and <a href="https://perldancer.org/">Dancer2</a></p>
|
|
||||||
</footer>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
@ -1,21 +0,0 @@
|
|||||||
<div class="text-center">
|
|
||||||
<a href="[% image_src %]"><img class="media-image" src="[% image_src %]" /></a>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<h2>metadata</h2>
|
|
||||||
|
|
||||||
<dl>
|
|
||||||
<dt>ID</dt>
|
|
||||||
<dd>[% media_id %]</dd>
|
|
||||||
<dt>Filename</dt>
|
|
||||||
<dd>[% media_filename %]</dd>
|
|
||||||
<dt>Size</dt>
|
|
||||||
<dd>[% media_size %]</dd>
|
|
||||||
<dt>Tags</dt>
|
|
||||||
[% UNLESS media_tags %]
|
|
||||||
<dd>no tags</dd>
|
|
||||||
[% END %]
|
|
||||||
[% FOREACH tag IN media_tags -%]
|
|
||||||
<dd><a href="[% tag.uri %]">[% tag.name %]</a></dd>
|
|
||||||
[% END -%]
|
|
||||||
</dl>
|
|
@ -1,5 +0,0 @@
|
|||||||
<p class="text-center">
|
|
||||||
[% FOREACH tag IN tags -%]
|
|
||||||
<a href="[% tag.uri %]">[% tag.name %] ([% tag.count %])</a>
|
|
||||||
[% END -%]
|
|
||||||
</p>
|
|
Loading…
Reference in New Issue
Block a user