Skip to content

Commit a7f8f89

Browse files
committed
rewrite profile page javascript to be bundled, and avoid jquery
1 parent 1fcebb5 commit a7f8f89

File tree

8 files changed

+258
-162
lines changed

8 files changed

+258
-162
lines changed

lib/MetaCPAN/Middleware/Static.pm

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ sub wrap {
5454
js/cpan.js
5555
js/github.js
5656
js/dropdown.js
57+
js/profile.js
5758
js/recaptcha.js
5859
js/search.js
5960
modules/bootstrap-v3.4.1/js/dropdown.js

lib/MetaCPAN/Web/Model/API/Author.pm

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -151,7 +151,7 @@ my $profile_data = {
151151
},
152152
hackerrank => {
153153
name => 'HackerRank',
154-
url_format => 'https://www.hackerrank.com/%s',
154+
url_format => 'https://www.hackerrank.com/profile/%s',
155155
},
156156
hackthissite => {
157157
name => 'HackThisSite',

lib/MetaCPAN/Web/Model/API/User.pm

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,17 @@ sub update_profile {
6262

6363
sub get_profile {
6464
my ( $self, $token ) = @_;
65-
$self->request( '/user/profile', undef, { access_token => $token } );
65+
$self->request( '/user/profile', undef, { access_token => $token } )
66+
->then( sub {
67+
my $data = shift;
68+
for my $field (qw(email website)) {
69+
my $value = $data->{$field}
70+
or next;
71+
$data->{$field} = [$value]
72+
if !ref $value;
73+
}
74+
return $data;
75+
} );
6676
}
6777

6878
sub add_favorite {

lib/MetaCPAN/Web/Test/HTML5/TreeBuilder.pm

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ sub start {
5555
local $HTML::TreeBuilder::isBodyElement{source} = 1;
5656
local $HTML::TreeBuilder::isBodyElement{summary} = 1;
5757
local $HTML::TreeBuilder::isBodyElement{svg} = 1;
58+
local $HTML::TreeBuilder::isBodyElement{template} = 1;
5859
local $HTML::TreeBuilder::isBodyElement{time} = 1;
5960
local $HTML::TreeBuilder::isBodyElement{track} = 1;
6061
local $HTML::TreeBuilder::isBodyElement{video} = 1;

root/account/profile.tx

Lines changed: 89 additions & 143 deletions
Large diffs are not rendered by default.

root/static/js/profile.js

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
window.addEventListener('DOMContentLoaded', function() {
2+
3+
if (!document.querySelector('.profile-form')) return;
4+
5+
"use strict";
6+
7+
function rewriteURL(link) {
8+
const url = link.dataset.urlTemplate;
9+
const input = link.parentNode.previousElementSibling;
10+
link.href = url.replace('%s', input.value);
11+
return true;
12+
}
13+
14+
for (const check_url of document.querySelectorAll('.account-settings .check-url')) {
15+
check_url.addEventListener('click', function(e) {
16+
return rewriteURL(this);
17+
});
18+
}
19+
20+
function removeDiv(div) {
21+
new Promise(resolve => {
22+
div.addEventListener("animationend", resolve);
23+
setTimeout(resolve, 400);
24+
}).then(() => div.parentNode.removeChild(div));
25+
div.classList.remove("slide-down");
26+
div.classList.add("slide-up");
27+
}
28+
29+
function removeField(e) {
30+
e.preventDefault();
31+
removeDiv(this.closest('.field-container'));
32+
}
33+
for (const remove_field of document.querySelectorAll('.account-settings .remove-field')) {
34+
remove_field.addEventListener('click', removeField);
35+
}
36+
37+
function removeProfile(e) {
38+
e.preventDefault();
39+
removeDiv(this.closest('.profile-container'));
40+
}
41+
42+
for (const remove_profile of document.querySelectorAll('.account-settings .remove-profile')) {
43+
remove_profile.addEventListener('click', removeProfile);
44+
}
45+
46+
function addProfile(container, id, title, formatUrl) {
47+
const profileNode = document.importNode(document.querySelector('#profile-tmpl').content, true);
48+
profileNode.querySelector('.remove-profile').addEventListener('click', removeProfile);
49+
if (id) {
50+
profileNode.querySelector('div.form-group').classList.add('profile', 'profile-' + id);
51+
profileNode.querySelector('.profile-title').innerText = title;
52+
const profile_name = profileNode.querySelector('input[name="profile.name"]');
53+
profile_name.value = id;
54+
profile_name.type = 'hidden';
55+
profileNode.querySelector('a.check-url').dataset.urlTemplate(formatUrl);
56+
57+
for (const check_url of profileNode.querySelectorAll(':scope .check-url')) {
58+
check_url.addEventListener('click', function(e) {
59+
return rewriteURL(this);
60+
});
61+
}
62+
}
63+
container.append(profileNode);
64+
}
65+
66+
document.querySelector('.account-settings .add-profile').addEventListener('change', function(e) {
67+
e.preventDefault();
68+
const option = this.selectedOptions[0];
69+
addProfile(
70+
document.querySelector('#metacpan_profiles'),
71+
this.value,
72+
option.dataset.title,
73+
option.dataset.urlFormat,
74+
);
75+
this.selectedIndex = 0;
76+
});
77+
78+
function addField(container, id) {
79+
const fieldNode = document.importNode(document.querySelector('template#field-tmpl').content, true);
80+
fieldNode.querySelector('input').name = id;
81+
fieldNode.querySelector('.remove-field').addEventListener('click', removeField);
82+
container.append(fieldNode);
83+
}
84+
85+
for (const btn of document.querySelectorAll('.account-settings button.add-field')) {
86+
btn.addEventListener('click', function (e) {
87+
e.preventDefault();
88+
addField(this.closest('.field-container').parentNode, this.dataset.fieldType);
89+
});
90+
}
91+
92+
93+
function validateJSON(input) {
94+
try {
95+
input.value && JSON.parse(input.value);
96+
input.classList.remove('invalid');
97+
} catch(err) {
98+
input.classList.add('invalid');
99+
}
100+
}
101+
102+
const extra = document.querySelector('.account-settings textarea[name="extra"]')
103+
extra.addEventListener('keyup', function (e) {
104+
validateJSON(this);
105+
});
106+
validateJSON(extra);
107+
108+
function fillLocation() {
109+
navigator.geolocation.getCurrentPosition((pos) => {
110+
document.querySelector('input[name="latitude"]').value = pos.coords.latitude;
111+
document.querySelector('input[name="longitude"]').value = pos.coords.longitude;
112+
}, function(){
113+
});
114+
return false;
115+
}
116+
117+
document.querySelector('.account-settings button.fill-location').addEventListener('click', function (e) {
118+
e.preventDefault();
119+
fillLocation();
120+
});
121+
122+
});

root/static/less/account.less

Lines changed: 10 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -7,24 +7,11 @@
77
td {
88
padding: 5px;
99
}
10-
.valid {
11-
background-color: #900;
12-
padding: 5px;
13-
float: right;
14-
font-size: 1.2em;
15-
color: #fff;
16-
font-weight: bold;
17-
display: none;
18-
}
1910

20-
.invalid {
21-
display: inline-block;
22-
}
23-
// To fix the author-pic float-right crap..
24-
.form-horizontal {
25-
.control-group:after {
26-
clear: left;
27-
}
11+
textarea.invalid {
12+
background:
13+
url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' version='1.1' height='18px' width='74px'><text text-anchor='end' x='74px' y='18' fill='white' font-size='18' font-weight='bold' font-family='&quot;Helvetica Neue&quot;, Helvetica, Arial, sans-serif'>invalid</text></svg>") bottom 0.2em right 0.2em/auto 1em no-repeat,
14+
linear-gradient(to bottom right, rgba(153,0,0,0) 90%, rgb(153, 0, 0) 95%);
2815
}
2916

3017
// to fix repeating fields like email etc
@@ -288,4 +275,10 @@
288275
float: none !important;
289276
}
290277
}
278+
279+
}
280+
@media (min-width: 480px) {
281+
.profile-form {
282+
.form-horizontal;
283+
}
291284
}

root/static/less/global.less

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -462,3 +462,26 @@ h1, .h1, h2, .h2, h3, .h3 {
462462
.page-content * {
463463
scroll-margin-top: 60px;
464464
}
465+
466+
@keyframes slide-down {
467+
0% { grid-template-rows: 0fr }
468+
100% { grid-template-rows: 1fr }
469+
}
470+
@keyframes slide-up {
471+
0% { grid-template-rows: 1fr }
472+
100% { grid-template-rows: 0fr }
473+
}
474+
.slide-out {
475+
display: grid;
476+
grid-template-rows: 1fr;
477+
}
478+
.slide-out.slide-down {
479+
animation: slide-down 0.4s;
480+
}
481+
.slide-out.slide-up {
482+
animation: slide-up 0.4s;
483+
grid-template-rows: 0fr;
484+
}
485+
.slide-out > div {
486+
overflow: hidden;
487+
}

0 commit comments

Comments
 (0)