From 412d7fbb503d96d19893581e8d516498536bc54b Mon Sep 17 00:00:00 2001 From: Graham Knop Date: Sun, 26 Nov 2017 00:30:41 +0100 Subject: [PATCH 1/6] allow sending form data via POST --- lib/MetaCPAN/Web/Model/API.pm | 39 +++++++++++++++++++++-------------- 1 file changed, 23 insertions(+), 16 deletions(-) diff --git a/lib/MetaCPAN/Web/Model/API.pm b/lib/MetaCPAN/Web/Model/API.pm index 2604ee69b5..706a45e242 100644 --- a/lib/MetaCPAN/Web/Model/API.pm +++ b/lib/MetaCPAN/Web/Model/API.pm @@ -15,6 +15,8 @@ use URI; use URI::QueryParam; use MetaCPAN::Web::Types qw( Uri ); use Try::Tiny qw( catch try ); +use HTTP::Request; +use HTTP::Request::Common (); my $loop; @@ -86,33 +88,38 @@ sub request { my $url = $self->api_secure->clone; + $method ||= $search ? 'POST' : 'GET'; + # the order of the following 2 lines matters # `path_query` is destructive $url->path_query($path); - for my $param ( keys %{ $params || {} } ) { - $url->query_param( $param => $params->{$param} ); - } my $current_url = $self->request_uri; my $request_id = $self->request_id; + if ( $method eq 'GET' || $search ) { + for my $param ( keys %{ $params || {} } ) { + $url->query_param( $param => $params->{$param} ); + } + } - my $request = HTTP::Request->new( + my $request = HTTP::Request::Common->can($method)->( + $url, ( - $method ? $method - : $search ? 'POST' - : 'GET', + $search + ? ( + 'Content-Type' => 'application/json', + 'Content' => encode_json($search), + ) + : $method eq 'POST' && $params ? ( + 'Content_Type' => 'multipart/form-data', + 'Content' => $params, + ) + : () ), - $url, - [ - ( $search ? ( 'Content-Type' => 'application/json' ) : () ), - ( $current_url ? ( 'Referer' => $current_url->as_string ) : () ), - ( $request_id ? ( 'X-MetaCPAN-Request-ID' => $request_id ) : () ), - ], + ( $current_url ? ( 'Referer' => $current_url->as_string ) : () ), + ( $request_id ? ( 'X-MetaCPAN-Request-ID' => $request_id ) : () ), ); - # encode_json returns an octet string - $request->add_content( encode_json($search) ) if $search; - $self->client->do_request( request => $request )->transform( done => sub { my $response = shift; From 4b512a6fbac2da6178aa71af26bae54d5c4a921f Mon Sep 17 00:00:00 2001 From: Graham Knop Date: Sun, 26 Nov 2017 01:54:40 +0100 Subject: [PATCH 2/6] page to render arbitrary pod to html --- lib/MetaCPAN/Web/Controller/Pod.pm | 59 ++++++++++++++++++++++++++- root/pod/pod2html.html | 17 ++++++++ root/static/images/gray.png | Bin 0 -> 68 bytes root/static/js/cpan.js | 62 ++++++++++++++++++++++++++++- root/static/less/pod2html.less | 7 ++++ root/static/less/style.less | 1 + 6 files changed, 143 insertions(+), 3 deletions(-) create mode 100644 root/pod/pod2html.html create mode 100644 root/static/images/gray.png create mode 100644 root/static/less/pod2html.less diff --git a/lib/MetaCPAN/Web/Controller/Pod.pm b/lib/MetaCPAN/Web/Controller/Pod.pm index 1b990dd119..7c130143cb 100644 --- a/lib/MetaCPAN/Web/Controller/Pod.pm +++ b/lib/MetaCPAN/Web/Controller/Pod.pm @@ -1,11 +1,13 @@ package MetaCPAN::Web::Controller::Pod; use HTML::Restrict; +use HTML::TokeParser; use Moose; use Try::Tiny; use URI; use HTML::Escape qw(escape_html); use Future; +use Encode (); use namespace::autoclean; @@ -170,6 +172,58 @@ sub view : Private { } } +sub pod2html : Path('/pod2html') { + my ( $self, $c ) = @_; + my $pod_file = $c->req->upload('pod_file'); + my $pod = $pod_file ? $pod_file->slurp : $c->req->parameters->{pod}; + + if ( defined $pod ) { + $c->stash( { pod => $pod } ); + my $encoded_pod = Encode::encode( 'UTF-8', $pod ); + my $html + = $c->model('API') + ->request( 'pod_render', undef, { pod => $encoded_pod }, 'POST' ) + ->get->{raw}; + $html = $self->filter_html($html); + my ( $pod_name, $abstract ); + my $p = HTML::TokeParser->new( \$html ); + while ( my $t = $p->get_token ) { + my ( $type, $tag, $attr ) = @$t; + if ( $type eq 'S' + && $tag eq 'h1' + && $attr->{id} + && $attr->{id} eq 'NAME' ) + { + my $name_section = $p->get_trimmed_text('h1'); + if ($name_section) { + ( $pod_name, $abstract ) + = $name_section =~ /(?:NAME\s+)?([^-]+)\s*-\s*(.*)/s; + } + last; + } + } + if ( $c->req->parameters->{raw} ) { + $c->res->content_type('text/plain'); + $c->res->header( 'X-Pod-Title' => $pod_name ) + if $pod_name; + $c->res->header( 'X-Pod-Abstract' => $abstract ) + if $abstract; + $c->res->body($html); + $c->detach; + } + else { + $c->stash( + { + pod_rendered => $html, + ( $pod_name ? ( pod_name => $pod_name ) : () ), + ( $abstract ? ( abstract => $abstract ) : () ), + } + ); + } + } + +} + sub filter_html { my ( $self, $html, $data ) = @_; @@ -236,7 +290,7 @@ sub filter_html { # bad protocol return ''; } - else { + elsif ($data) { my $base = "https://st.aticpan.org/source/"; if ( $val =~ s{^/}{} ) { $base .= "$data->{author}/$data->{release}/"; @@ -247,6 +301,9 @@ sub filter_html { } $val = URI->new_abs( $val, $base )->as_string; } + else { + $val = '/static/images/gray.png'; + } } $tag .= qq{ $attr="} . escape_html($val) . qq{"}; } diff --git a/root/pod/pod2html.html b/root/pod/pod2html.html new file mode 100644 index 0000000000..159beb9e6c --- /dev/null +++ b/root/pod/pod2html.html @@ -0,0 +1,17 @@ +<% + title = 'Pod Renderer' _ (pod_name ? ' - ' _ pod_name : '') +%> +
+
+
+
+
+
+
+
+
<% pod_rendered | none %> + <% IF pod_error %> +

Error rendering POD - <% pod_error %>

+ <% END %> +
+
diff --git a/root/static/images/gray.png b/root/static/images/gray.png new file mode 100644 index 0000000000000000000000000000000000000000..9d5fcba809b8de3b52041f208d8483258b19dd90 GIT binary patch literal 68 zcmeAS@N?(olHy`uVBq!ia0vp^j3CUx0wlM}@Gt=>Zci7-kcwN$cmB#VFjX_|>3Xkr Q7AVZ%>FVdQ&MBb@0G2)vJpcdz literal 0 HcmV?d00001 diff --git a/root/static/js/cpan.js b/root/static/js/cpan.js index d2382b2d1f..476fd1013d 100644 --- a/root/static/js/cpan.js +++ b/root/static/js/cpan.js @@ -302,8 +302,7 @@ $(document).ready(function() { $('.dropdown-toggle').dropdown(); - var index = $("#index"); - if (index) { + function format_index(index) { index.wrap('
'); var container = index.parent().parent(); @@ -328,6 +327,10 @@ $(document).ready(function() { container.addClass("pull-right"); } } + var index = $("#index"); + if (index.length) { + format_index(index); + } ['right'].forEach(function(side) { var panel = $('#' + side + "-panel"); @@ -358,6 +361,61 @@ $(document).ready(function() { if (changes.prop('scrollHeight') > changes.height()) { $("#last-changes-toggle").show(); } + + var pod2html_form = $('#metacpan-pod-renderer-form'); + var pod2html_text = $('[name="pod"]', pod2html_form); + var pod2html_update = function(pod) { + if (!pod) { + pod = pod2html_text.get(0).value; + } + var rendered = $('#metacpan-pod-rendered'); + rendered.html('

Loading...

'); + document.title = "Pod Renderer - metacpan.org"; + $.ajax({ + url: '/pod2html', + method: 'POST', + data: { + pod: pod, + raw: true + }, + success: function(data, stat, req) { + rendered.html(data); + var title = req.getResponseHeader('X-Pod-Title'); + if (title) { + document.title = "Pod Renderer - " + title + " - metacpan.org"; + } + var index = $("#index"); + if (index.length) { + format_index(index); + } + }, + error: function(data, stat) { + rendered.html('

Error rendering POD' + + (data && data.length ? ' - ' + data : '') + + '

'); + } + }); + }; + if (window.FileReader) { + $('input[type="file"]', pod2html_form).on('change', function(e) { + var files = this.files; + for (var i = 0; i < files.length; i++) { + var file = files[i]; + var reader = new FileReader(); + reader.onload = function(e) { + pod2html_text.get(0).value = e.target.result; + pod2html_update(pod2html_text.get(0).value); + }; + reader.readAsText(file); + } + this.value = null; + }); + } + pod2html_form.on('submit', function(e) { + e.preventDefault(); + e.stopPropagation(); + pod2html_update(); + }); }); function set_page_size(selector, storage_name) { diff --git a/root/static/less/pod2html.less b/root/static/less/pod2html.less new file mode 100644 index 0000000000..ed1de6cc5d --- /dev/null +++ b/root/static/less/pod2html.less @@ -0,0 +1,7 @@ +#metacpan-pod-renderer-form { + textarea { + font-family: @font-family-monospace; + width: 600px; + height: 200px; + } +} diff --git a/root/static/less/style.less b/root/static/less/style.less index 1b5ab621ea..cabec8fba7 100644 --- a/root/static/less/style.less +++ b/root/static/less/style.less @@ -27,6 +27,7 @@ @import "home.less"; @import "syntaxhighlighter.less"; @import "login.less"; +@import "pod2html.less"; @linkColor: #3366cc; @textColor: @black; From 07ce47a83923dd6f7c5ce73868727a6027be0f6b Mon Sep 17 00:00:00 2001 From: Graham Knop Date: Sun, 3 Dec 2017 23:25:53 +0100 Subject: [PATCH 3/6] improve encoding handling of pod2html --- lib/MetaCPAN/Web/Controller/Pod.pm | 68 ++++++++++++++++-------------- 1 file changed, 37 insertions(+), 31 deletions(-) diff --git a/lib/MetaCPAN/Web/Controller/Pod.pm b/lib/MetaCPAN/Web/Controller/Pod.pm index 7c130143cb..ec58cc432e 100644 --- a/lib/MetaCPAN/Web/Controller/Pod.pm +++ b/lib/MetaCPAN/Web/Controller/Pod.pm @@ -7,7 +7,7 @@ use Try::Tiny; use URI; use HTML::Escape qw(escape_html); use Future; -use Encode (); +use Encode qw( encode decode DIE_ON_ERR LEAVE_SRC ); use namespace::autoclean; @@ -174,17 +174,35 @@ sub view : Private { sub pod2html : Path('/pod2html') { my ( $self, $c ) = @_; - my $pod_file = $c->req->upload('pod_file'); - my $pod = $pod_file ? $pod_file->slurp : $c->req->parameters->{pod}; - - if ( defined $pod ) { - $c->stash( { pod => $pod } ); - my $encoded_pod = Encode::encode( 'UTF-8', $pod ); - my $html - = $c->model('API') - ->request( 'pod_render', undef, { pod => $encoded_pod }, 'POST' ) - ->get->{raw}; - $html = $self->filter_html($html); + my $pod; + if ( my $pod_file = $c->req->upload('pod_file') ) { + my $raw_pod = $pod_file->slurp; + eval { + $pod = decode( 'UTF-8', $raw_pod, DIE_ON_ERR | LEAVE_SRC ); + 1; + } or $pod = decode( 'cp1252', $raw_pod ); + } + elsif ( $pod = $c->req->parameters->{pod} ) { + } + else { + return; + } + + $c->stash( { pod => $pod } ); + + my $html + = $c->model('API') + ->request( 'pod_render', undef, { pod => encode( 'UTF-8', $pod ) }, + 'POST' )->get->{raw}; + + $html = $self->filter_html($html); + + if ( $c->req->parameters->{raw} ) { + $c->res->content_type('text/html'); + $c->res->body($html); + $c->detach; + } + else { my ( $pod_name, $abstract ); my $p = HTML::TokeParser->new( \$html ); while ( my $t = $p->get_token ) { @@ -202,26 +220,14 @@ sub pod2html : Path('/pod2html') { last; } } - if ( $c->req->parameters->{raw} ) { - $c->res->content_type('text/plain'); - $c->res->header( 'X-Pod-Title' => $pod_name ) - if $pod_name; - $c->res->header( 'X-Pod-Abstract' => $abstract ) - if $abstract; - $c->res->body($html); - $c->detach; - } - else { - $c->stash( - { - pod_rendered => $html, - ( $pod_name ? ( pod_name => $pod_name ) : () ), - ( $abstract ? ( abstract => $abstract ) : () ), - } - ); - } + $c->stash( + { + pod_rendered => $html, + ( $pod_name ? ( pod_name => $pod_name ) : () ), + ( $abstract ? ( abstract => $abstract ) : () ), + } + ); } - } sub filter_html { From 61fbb20a6bbebe13052389b6e9df51ccd4cbf2cd Mon Sep 17 00:00:00 2001 From: Graham Knop Date: Sun, 3 Dec 2017 23:26:57 +0100 Subject: [PATCH 4/6] update error and loading handling of pod2html --- root/pod/pod2html.html | 8 ++++--- root/static/js/cpan.js | 48 ++++++++++++++++++++++++++++++------------ 2 files changed, 39 insertions(+), 17 deletions(-) diff --git a/root/pod/pod2html.html b/root/pod/pod2html.html index 159beb9e6c..c3f6259454 100644 --- a/root/pod/pod2html.html +++ b/root/pod/pod2html.html @@ -9,9 +9,11 @@
-
<% pod_rendered | none %> + +
<% pod_rendered | none %>
<% IF pod_error %> -

Error rendering POD - <% pod_error %>

+
Error rendering POD - <% pod_error %>
+ <% ELSE %> + <% END %> -
diff --git a/root/static/js/cpan.js b/root/static/js/cpan.js index 476fd1013d..845b104d33 100644 --- a/root/static/js/cpan.js +++ b/root/static/js/cpan.js @@ -262,13 +262,16 @@ $(document).ready(function() { } } - $('.anchors').find('h1,h2,h3,h4,h5,h6,dt').each(function() { - if (this.id) { - $(document.createElement('a')).attr('href', '#' + this.id).addClass('anchor').append( - $(document.createElement('span')).addClass('fa fa-bookmark black') - ).prependTo(this); - } - }); + function create_anchors(top) { + top.find('h1,h2,h3,h4,h5,h6,dt').each(function() { + if (this.id) { + $(document.createElement('a')).attr('href', '#' + this.id).addClass('anchor').append( + $(document.createElement('span')).addClass('fa fa-bookmark black') + ).prependTo(this); + } + }); + } + create_anchors($('.anchors')); var module_source_href = $('#source-link').attr('href'); if (module_source_href) { @@ -368,8 +371,15 @@ $(document).ready(function() { if (!pod) { pod = pod2html_text.get(0).value; } + var submit = pod2html_form.find('input[type="submit"]'); + submit.attr("disabled", "disabled"); var rendered = $('#metacpan-pod-rendered'); - rendered.html('

Loading...

'); + var loading = $('#metacpan-pod-renderer-loading'); + var error = $('#metacpan-pod-renderer-error'); + rendered.hide(); + rendered.html(''); + loading.show(); + error.hide(); document.title = "Pod Renderer - metacpan.org"; $.ajax({ url: '/pod2html', @@ -380,19 +390,29 @@ $(document).ready(function() { }, success: function(data, stat, req) { rendered.html(data); - var title = req.getResponseHeader('X-Pod-Title'); - if (title) { + loading.hide(); + error.hide(); + var res = $('#NAME + p').text().match(/^([^-]+?)\s*-\s*(.*)/); + if (res) { + var title = res[0]; + var abstract = res[1]; document.title = "Pod Renderer - " + title + " - metacpan.org"; } - var index = $("#index"); + var index = $("#index", rendered); if (index.length) { format_index(index); } + create_anchors(rendered); + rendered.show(); + submit.removeAttr("disabled"); }, error: function(data, stat) { - rendered.html('

Error rendering POD' + - (data && data.length ? ' - ' + data : '') + - '

'); + rendered.hide(); + loading.hide(); + error.html('Error rendering POD' + + (data && data.length ? ' - ' + data : '')); + error.show(); + submit.removeAttr("disabled"); } }); }; From eb84414d2bb1824d1d5bbd1e247cc7861289d88d Mon Sep 17 00:00:00 2001 From: Graham Knop Date: Thu, 7 Dec 2017 16:16:13 +0100 Subject: [PATCH 5/6] pod renderer style improvements --- root/pod/pod2html.html | 41 +++++++++++++++++--------- root/static/js/cpan.js | 2 +- root/static/less/pod2html.less | 53 ++++++++++++++++++++++++++++++---- 3 files changed, 76 insertions(+), 20 deletions(-) diff --git a/root/pod/pod2html.html b/root/pod/pod2html.html index c3f6259454..6a989a929f 100644 --- a/root/pod/pod2html.html +++ b/root/pod/pod2html.html @@ -1,19 +1,32 @@ <% title = 'Pod Renderer' _ (pod_name ? ' - ' _ pod_name : '') %> -
-
-
-
-
-
-
+
+
+ +
+
+
+ +
+ + +
+
+ + +
+
+ +
style="display: none"<% END %>> + Error rendering POD<% IF pod_error; ' - ' _ pod_error; END %> +
+
+
- -
<% pod_rendered | none %>
- <% IF pod_error %> -
Error rendering POD - <% pod_error %>
- <% ELSE %> - - <% END %> +
+
+
style="display: none"<% END %>><% pod_rendered | none %>
diff --git a/root/static/js/cpan.js b/root/static/js/cpan.js index 845b104d33..25ec64c02d 100644 --- a/root/static/js/cpan.js +++ b/root/static/js/cpan.js @@ -373,7 +373,7 @@ $(document).ready(function() { } var submit = pod2html_form.find('input[type="submit"]'); submit.attr("disabled", "disabled"); - var rendered = $('#metacpan-pod-rendered'); + var rendered = $('#metacpan-pod-renderer-output'); var loading = $('#metacpan-pod-renderer-loading'); var error = $('#metacpan-pod-renderer-error'); rendered.hide(); diff --git a/root/static/less/pod2html.less b/root/static/less/pod2html.less index ed1de6cc5d..c686f11061 100644 --- a/root/static/less/pod2html.less +++ b/root/static/less/pod2html.less @@ -1,7 +1,50 @@ -#metacpan-pod-renderer-form { - textarea { - font-family: @font-family-monospace; - width: 600px; - height: 200px; +#metacpan-pod-renderer-pod { + font-family: @font-family-monospace; + width: 100%; + height: 200px; + border-radius: 5px; + color: #333; +} + +#metacpan-pod-renderer-file { + opacity: 0; + width: 0; + height: 0; + padding: 0; + margin: 0; + position: absolute; +} + +.metacpan-pod-renderer { + overflow: auto; + + .button-line { + text-align: right; + } + + .form-group:last-child { + margin-bottom: 0px; + } + + .alert { + margin-top: 15px; + margin-bottom: 0px; + } + + .panel-heading { + .fa-chevron-down { + display: none; + } + .fa-chevron-up { + display: inline-block; + } + &.collapsed { + .fa-chevron-down { + display: inline-block; + } + .fa-chevron-up { + display: none; + } + } } } From d2919c384645dd17dbd3e9603146df0f3f2e7d04 Mon Sep 17 00:00:00 2001 From: Graham Knop Date: Mon, 11 Dec 2017 09:40:11 -0600 Subject: [PATCH 6/6] lab -> tools --- lib/MetaCPAN/Web/Controller/Lab.pm | 3 ++- lib/MetaCPAN/Web/Controller/Tools.pm | 14 ++++++++++++++ root/{lab/list.html => inc/tools-bar.html} | 11 +++++++---- root/lab.html | 18 ------------------ root/lab/dashboard.html | 2 +- root/pod/pod2html.html | 1 + root/tools.html | 18 ++++++++++++++++++ root/wrapper.html | 6 +++--- 8 files changed, 46 insertions(+), 27 deletions(-) create mode 100644 lib/MetaCPAN/Web/Controller/Tools.pm rename root/{lab/list.html => inc/tools-bar.html} (50%) delete mode 100644 root/lab.html create mode 100644 root/tools.html diff --git a/lib/MetaCPAN/Web/Controller/Lab.pm b/lib/MetaCPAN/Web/Controller/Lab.pm index d43fcf2263..8d5611f9d0 100644 --- a/lib/MetaCPAN/Web/Controller/Lab.pm +++ b/lib/MetaCPAN/Web/Controller/Lab.pm @@ -18,7 +18,8 @@ __PACKAGE__->config( sub lab : Path : Args(0) { my ( $self, $c ) = @_; - $c->stash( template => 'lab.html' ); + $c->res->redirect( '/tools', 301 ); + $c->detach; } sub dependencies : Local : Args(0) : Does('Sortable') { diff --git a/lib/MetaCPAN/Web/Controller/Tools.pm b/lib/MetaCPAN/Web/Controller/Tools.pm new file mode 100644 index 0000000000..ed97107c40 --- /dev/null +++ b/lib/MetaCPAN/Web/Controller/Tools.pm @@ -0,0 +1,14 @@ +package MetaCPAN::Web::Controller::Tools; + +use Moose; +use namespace::autoclean; + +BEGIN { extends 'MetaCPAN::Web::Controller' } + +sub tools : Path : Args(0) { + my ( $self, $c ) = @_; + $c->stash( template => 'tools.html' ); +} + +__PACKAGE__->meta->make_immutable; +1; diff --git a/root/lab/list.html b/root/inc/tools-bar.html similarity index 50% rename from root/lab/list.html rename to root/inc/tools-bar.html index 783cd1e78d..da0ddbd24b 100644 --- a/root/lab/list.html +++ b/root/inc/tools-bar.html @@ -1,12 +1,15 @@
diff --git a/root/lab.html b/root/lab.html deleted file mode 100644 index 2c052fd9f1..0000000000 --- a/root/lab.html +++ /dev/null @@ -1,18 +0,0 @@ -<% PROCESS "lab/list.html" %> - -
-

LAB - YOU ARE IN A TESTING ZONE

- -<% USE Markdown -%> -<% FILTER markdown %> -## Experimental features - -Try them and let us know how to improve them. - -* Dashboard - a personalised dashboard (for logged in authors) -* Dependencies - list the dependencies of a module - -<% END %> - -
- diff --git a/root/lab/dashboard.html b/root/lab/dashboard.html index 644be5b626..19a1d04f43 100644 --- a/root/lab/dashboard.html +++ b/root/lab/dashboard.html @@ -1,4 +1,4 @@ -<% PROCESS "lab/list.html" %> +<% PROCESS "inc/tools-bar.html" %>
diff --git a/root/pod/pod2html.html b/root/pod/pod2html.html index 6a989a929f..f47ec27020 100644 --- a/root/pod/pod2html.html +++ b/root/pod/pod2html.html @@ -1,6 +1,7 @@ <% title = 'Pod Renderer' _ (pod_name ? ' - ' _ pod_name : '') %> +<% PROCESS "inc/tools-bar.html" %>