source: bookmarks/trunk/lib/Bookmarks/List.pm @ 106

Last change on this file since 106 was 106, checked in by peter, 9 years ago

#6: Added basic pagination to the bookmarks HTML list view.

File size: 7.1 KB
Line 
1package Bookmarks::List;
2
3use Moose;
4
5use Encode;
6use HTTP::Date qw{time2iso time2isoz};
7
8has bookmarks => (
9    is => 'ro',
10    isa => 'Bookmarks',
11);
12has search => (
13    is => 'ro',
14    isa => 'Bookmarks::Search',
15    handles => [qw{query tags limit offset results}],
16);
17has title => (
18    is => 'ro',
19    builder => '_build_title',
20    lazy => 1,
21);
22
23sub _get_list_links {
24    my $self = shift;
25    my ($self_type, $query) = @_;
26
27    # remove extraneous blank ?q= parameters
28    delete $query->{q} if defined $query->{q} && $query->{q} eq '';
29   
30    my @links = (
31        {
32            text => 'JSON',
33            type => 'application/json',
34            query => {
35                %$query,
36                format => 'json',
37            },
38        },
39        {
40            text => 'XBEL',
41            type => 'application/xml',
42            query => {
43                %$query,
44                format => 'xbel',
45            },
46        },
47        {
48            text => 'Atom',
49            type => 'application/atom+xml',
50            path => 'feed',
51            query => {
52                %$query,
53            },
54        },
55        {
56            text => 'CSV',
57            type => 'text/csv',
58            query => {
59                %$query,
60                format => 'csv',
61            },
62        },
63        {
64            text => 'URI List',
65            type => 'text/uri-list',
66            query => {
67                %$query,
68                format => 'text',
69            },
70        },
71        {
72            text => 'HTML',
73            type => 'text/html',
74            query => {
75                %$query,
76            },
77        },
78    );
79
80    for my $link (@links) {
81        $link->{rel}  = $link->{type} eq $self_type ? 'self' : 'alternate';
82        $link->{href} = URI->new_abs($link->{path} || '', $self->bookmarks->base_uri);
83        $link->{href}->query_form($link->{query});
84    }
85
86    return @links;
87}
88
89sub _build_title {
90    my $self = shift;
91    return 'Bookmarks' . (@{ $self->tags } ? " tagged as " . join(' & ', @{ $self->tags }) : '') . ($self->query ? " matching '" . $self->query . "'" : '');
92}
93
94sub as_json {
95    my $self = shift;
96    require JSON;
97    my $json = decode_utf8(
98        JSON->new->utf8->convert_blessed->encode({
99            bookmarks => [ map { $_->to_hashref } @{ $self->results } ],
100        })
101    );
102    return [200, ['Content-Type' => 'application/json; charset=UTF-8'], [$json]];
103}
104
105sub as_xbel {
106    my $self = shift;
107    require XML::XBEL;
108    #TODO: conditional support; if XML::XBEL is not present, return a 5xx response
109
110    my $xbel = XML::XBEL->new;
111
112    $xbel->new_document({
113        title => $self->title,
114    });
115
116    for my $bookmark (@{ $self->results }) {
117        my $cdatetime = time2isoz $bookmark->ctime;
118        my $mdatetime = time2isoz $bookmark->mtime;
119        # make the timestamps W3C-correct
120        s/ /T/ foreach ($cdatetime, $mdatetime);
121
122        $xbel->add_bookmark({
123            href     => $bookmark->uri,
124            title    => $bookmark->title,
125            desc     => 'Tags: ' . join(', ', @{ $bookmark->tags }),
126            added    => $cdatetime,
127            #XXX: are we sure that modified is the mtime of the bookmark or the resource?
128            modified => $mdatetime,
129        });
130    }
131
132    return [200, ['Content-Type' => 'application/xml; charset=UTF-8'], [$xbel->toString]];
133}
134
135sub as_text {
136    my $self = shift;
137    my $text = join '', 
138        map {
139            sprintf "# %s\n# Tags: %s\n%s\n",
140            $_->title,
141            join(', ', @{ $_->tags }), 
142            $_->uri
143        } @{ $self->results };
144    return [200, ['Content-Type' => 'text/uri-list; charset=UTF-8'], [$text]];
145}
146
147sub as_csv {
148    my $self = shift;
149    require Text::CSV::Encoded;
150    my $csv = Text::CSV::Encoded->new({ encoding_out => 'utf8' });
151    my $text = qq{id,uri,title,tags,ctime,mtime\n};
152    for my $bookmark (@{ $self->results }) {
153        my $success = $csv->combine(
154            $bookmark->id,
155            $bookmark->uri,
156            $bookmark->title,
157            join(' ', @{ $bookmark->tags }),
158            $bookmark->ctime,
159            $bookmark->mtime,
160        );
161        $text .= $csv->string . "\n" if $success;
162    }
163
164    # include the local timestamp in the attchment filename
165    my $dt = time2iso;
166    $dt =~ s/[^\d]//g;
167
168    my $filename = sprintf(
169        'bookmarks-%s-%s.csv',
170        join('_', @{ $self->tags }),
171        $dt,
172    );
173
174    return [200, ['Content-Type' => 'text/csv; charset=UTF-8', 'Content-Disposition' => sprintf('attachement; filename="%s"', $filename)], [$text]];
175}
176
177sub as_html {
178    my $self = shift;
179
180    require Template;
181    require File::Basename;
182    my $template = Template->new({ INCLUDE_PATH => File::Basename::dirname($INC{'Bookmarks/List.pm'}) });
183
184    my @all_tags = $self->bookmarks->get_tags({ selected => @{ $self->tags }[0] });
185    my @cotags = $self->bookmarks->get_cotags({ search => $self->search });
186
187    $template->process(
188        'list.tt',
189        {
190            base_url     => $self->bookmarks->base_uri,
191            title        => $self->title,
192            query        => $self->query,
193            selected_tag => @{ $self->tags }[0],
194            search_tags  => $self->tags,
195            links        => [ $self->_get_list_links('text/html', { q => $self->query, tag => $self->tags }) ],
196            all_tags     => \@all_tags,
197            cotags       => \@cotags,
198            resources    => $self->results,
199            pages        => $self->search->pages,
200        },
201        \my $output,
202    );
203    return [200, ['Content-Type' => 'text/html; charset=UTF-8'], [$output]];
204}
205
206sub as_atom {
207    my $self = shift;
208
209    require XML::Atom;
210    $XML::Atom::DefaultVersion = "1.0";
211
212    require XML::Atom::Feed;
213    require XML::Atom::Entry;
214    require XML::Atom::Link;
215    require XML::Atom::Category;
216
217    my $feed = XML::Atom::Feed->new;
218    $feed->title($self->title);
219
220    for my $link ($self->_get_list_links('application/atom+xml', { q => $self->query, tag => $self->tags })) {
221        my $atom_link = XML::Atom::Link->new;
222        $atom_link->type($link->{type});
223        $atom_link->rel($link->{rel});
224        $atom_link->href($link->{href}->canonical);
225        $feed->add_link($atom_link);
226    }
227
228    for my $bookmark (@{ $self->results }) {
229        my $entry = XML::Atom::Entry->new;
230        $entry->id($bookmark->bookmark_uri->canonical);
231        $entry->title($bookmark->title);
232       
233        my $link = XML::Atom::Link->new;
234        $link->href($bookmark->uri);
235        $entry->add_link($link);
236       
237        $entry->summary('Tags: ' . join(', ', @{ $bookmark->tags }));
238
239        my $cdatetime = time2isoz $bookmark->ctime;
240        my $mdatetime = time2isoz $bookmark->mtime;
241        # make the timestamp W3C-correct
242        s/ /T/ foreach ($cdatetime, $mdatetime);
243        $entry->published($cdatetime);
244        $entry->updated($mdatetime);
245       
246        for my $tag (@{ $bookmark->tags }) {
247            my $category = XML::Atom::Category->new;
248            $category->term($tag);
249            $entry->add_category($category);
250        }
251
252        $feed->add_entry($entry);
253    }
254
255    return [200, ['Content-Type' => 'application/atom+xml; charset=UTF-8'], [$feed->as_xml]];
256}
257
2581;
Note: See TracBrowser for help on using the repository browser.